WebSocket Server: Display Sensor Readings

In this guide, you'll learn how to create a WebSocket server with the ESP32 to display sensor readings on a web page. Whenever the ESP32 has new readings available, the web page is updated automatically without the need to manually refresh it.

Introducing WebSocket Protocol

A WebSocket is a persistent connection between a client and a server that allows bidirectional communication between both parties using a TCP connection. This means you can send data from the client to the server and from the server to the client at any given time. The client establishes a WebSocket connection with the server through a process known as WebSocket handshake. The handshake starts with an HTTP request/response, allowing servers to handle HTTP connections as well as WebSocket connections on the same port. Once the connection is established, the client and the server can send WebSocket data in full duplex mode. Using the WebSockets protocol, the server (ESP32 board) can send information to the client or to all clients without being requested. This also allows us to send information to the web browser when a change occurs. This change can be something that happened on the web page (you clicked a button) or something that happened on the ESP32 side like pressing a physical button on a circuit, or new sensor readings available. Learn how to control the ESP32 outputs via WebSocket protocol: ESP32 WebSocket Server: Control Outputs (Arduino IDE).

Project Overview

Here's the web page we'll build for this project. We'll create a web page that displays temperature, humidity, and pressure. The web page displays the latest sensor readings when you open or refresh the web page. The sensor readings update automatically whenever there's a new reading available on the ESP32 without the need to refresh the webpage. The web server works perfectly on multiple clients (multiple web browser tabs on the same device or different devices).

How does it work?

The ESP hosts a web server that displays a web page with three cards for the sensor readings. When you open the webpage, it sends a message (getReadings) to the ESP via WebSocket protocol. The server (ESP) receives that message. When that happens, it gets new readings from the sensors and sends them back to the client (web browser), also via web socket protocol. This way, whenever you open a new tab, it always shows the current and updated values. Every 30 seconds, the ESP gets new readings and sends them to all connected clients (all web browser tabs opened) via WebSocket protocol. The client receives that message and displays the readings on the web page.

Prerequisites

Before proceeding with this tutorial, make sure you check all the following prerequisites.

1) Parts Required

To follow this project you need: ESP32 Board read ESP32 Development Boards Review and Comparison BME280 sensor module check the BME280 getting started guide with the ESP32 Breadboard Jumper wires For this example, we'll use a BME280 sensor, but you can use any other sensor you're familiar with. You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

2) Arduino IDE and ESP32 Boards Add-on

We'll program the ESP32 using Arduino IDE. So, you must have the ESP32 add-on installed. Follow the next tutorial if you haven't already: Installing ESP32 Board in Arduino IDE (Windows, Mac OS X, Linux) If you want to use VS Code with the PlatformIO extension, follow the next tutorial instead to learn how to program the ESP32: Getting Started with VS Code and PlatformIO IDE for ESP32 and ESP8266 (Windows, Mac OS X, Linux Ubuntu)

3) Filesystem Uploader Plugin

To upload the HTML, CSS, and JavaScript files needed to build this project to the ESP32 flash memory (SPIFFS), we'll use a plugin for Arduino IDE: SPIFFS Filesystem uploader. Follow the next tutorial to install the filesystem uploader plugin if you haven't already: ESP32: Install SPIFFS FileSystem Uploader Plugin in Arduino IDE If you're using VS Code with the PlatformIO extension, read the following tutorial to learn how to upload files to the filesystem: ESP32 with VS Code and PlatformIO: Upload Files to Filesystem (SPIFFS)

4) Libraries

To build this project, you need to install the following libraries: Arduino_JSON library by Arduino version 0.1.0 (Arduino Library Manager) Adafruit_BME280 (Arduino Library Manager) ESPAsyncWebServer (.zip folder) AsyncTCP (.zip folder) You can install the first two libraries using the Arduino Library Manager. Go to Sketch > Include Library > Manage Libraries and search for the library name. The ESPAsyncWebServer and AsynTCP libraries aren't available to install through the Arduino Library Manager, so you need to copy the library files to the Arduino Installation Libraries folder. Alternatively, in your Arduino IDE, you can go to Sketch> Include Library > Add .zip Library and select the libraries you've just downloaded.

Installing Libraries (VS Code + PlatformIO)

If you're programming the ESP32 using PlatformIO, you should add the following lines to the platformio.ini file to include the libraries (also change the Serial Monitor speed to 115200): platform = [email protected] board = esp32doit-devkit-v1 framework = arduino monitor_speed = 115200 lib_deps = ESP Async WebServer arduino-libraries/Arduino_JSON @ 0.1.0 adafruit/Adafruit BME280 Library @ ^2.1.0 adafruit/Adafruit Unified Sensor @ ^1.1.4

Building the Circuit

To exemplify how to display sensor readings on a web server with the ESP32, we'll send sensor readings from a BME280 sensor to the browser. So, you need to wire a BME280 sensor to your ESP32. You can also use any other sensor you're familiar with.

Schematic Diagram

We're going to use I2C communication with the BME280 sensor module. For that, wire the sensor to the default ESP32 SCL (GPIO 22) and SDA (GPIO 21) pins, as shown in the following schematic diagram. Recommended reading: ESP32 Pinout Reference: Which GPIO pins should you use?

Organizing Your Files

To keep the project organized and make it easier to understand, we'll create four files to build the web server: Arduino sketch: to get the sensor readings and handle the web server; index.html: to define the content of the web page to display the sensor readings; style.css: to style the web page; script.js: to program the behavior of the web pagehandle what happens when you open the web page and display the readings received via WebSocket protocol. You should save the HTML, CSS, and JavaScript files inside a folder called data inside the Arduino sketch folder, as shown in the previous diagram. We'll upload these files to the ESP32 filesystem (SPIFFS). You can download all project files: Download all the project files

HTML File

Copy the following to the index.html file. <!DOCTYPE html> <html> <head> <title>ESP IOT DASHBOARD</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/png" href="favicon.png"> <link rel="stylesheet" type="text/css" href="style.css"> </head> <body> <div> <h1>SENSOR READINGS (WEBSOCKET)</h2> </div> <div> <div> <div> <p><i style="color:#059e8a;"></i> Temperature</p> <p><span></span> °C</p> </div> <div> <p> Humidity</p> <p><span></span> %</p> </div> <div> <p> Pressure</p> <p><span></span> hpa</p> </div> </div> </div> <script src="script.js"></script> </body> </html> View raw code We won't go into much detail about the content of the HTML file. Just the relevant parts. The following lines display a card for the temperature. <div> <p><i style="color:#059e8a;"></i> Temperature</p> <p><span></span> °C</p> </div> The temperature will show up in the following paragraph between the <span> tags. Notice that you need a unique id for that HTML tag so that later we know how to refer to this HTML element. In this case, the unique id is temperature. <span></span> We do a similar procedure for the humidity and pressure. The unique ids for the HTML elements where we'll display the humidity and pressure are humidity and pressure. <div> <p> Humidity</p> <p><span></span> %</p> </div> <div> <p> Pressure</p> <p><span></span> hpa</p> </div>

CSS File

Copy the following to the style.css file. Feel free to change it to make the web page look as you wish. We won't explain how the CSS for this web page works because it is not relevant for this tutorial. html { font-family: Arial, Helvetica, sans-serif; display: inline-block; text-align: center; } h1 { font-size: 1.8rem; color: white; } .topnav { overflow: hidden; background-color: #0A1128; } body { margin: 0; } .content { padding: 50px; } .card-grid { max-width: 800px; margin: 0 auto; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); } .card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); } .card-title { font-size: 1.2rem; font-weight: bold; color: #034078 } .reading { font-size: 1.2rem; color: #1282A2; } View raw code

JavaScript File

Copy the following to the script.js file. var gateway = `ws://${window.location.hostname}/ws`; var websocket; // Init web socket when the page loads window.addEventListener('load', onload); function onload(event) { initWebSocket(); } function getReadings(){ websocket.send("getReadings"); } function initWebSocket() { console.log('Trying to open a WebSocket connection'); websocket = new WebSocket(gateway); websocket.onopen = onOpen; websocket.onclose = onClose; websocket.onmessage = onMessage; } // When websocket is established, call the getReadings() function function onOpen(event) { console.log('Connection opened'); getReadings(); } function onClose(event) { console.log('Connection closed'); setTimeout(initWebSocket, 2000); } // Function that receives the message from the ESP32 with the readings function onMessage(event) { console.log(event.data); var myObj = JSON.parse(event.data); var keys = Object.keys(myObj); for (var i = 0; i < keys.length; i++){ var key = keys[i]; document.getElementById(key).innerHTML = myObj[key]; } } View raw code Here's a list of what this code does: initializes a WebSocket connection with the server; sends a message to the server to get the current sensor readings; uses the response to update the sensor readings on the web page; handles data exchange through the WebSocket protocol. Let's take a look at this JavaScript code to see how it works. The gateway is the entry point to the WebSocket interface. window.location.hostname gets the current page address (the web server IP address). var gateway = ws://${window.location.hostname}/ws; Create a new global variable called websocket. var websocket; Add an event listener that will call the onload function when the web page loads. window.addEventListener('load', onload); The onload() function calls the initWebSocket() function to initialize a WebSocket connection with the server. function onload(event) { initWebSocket(); } The initWebSocket() function initializes a WebSocket connection on the gateway defined earlier. We also assign several callback functions for when the WebSocket connection is opened, closed, or when a message is received. function initWebSocket() { console.log('Trying to open a WebSocket connection'); websocket = new WebSocket(gateway); websocket.onopen = onOpen; websocket.onclose = onClose; websocket.onmessage = onMessage; } Note that when the WebSocket connection is open, we'll call the getReadings function. function onOpen(event) { console.log('Connection opened'); getReadings(); } The getReadings() function sends a message to the server getReadings to get the current sensor readings. Then, we must handle what happens when we receive that message on the server side (ESP32). function getReadings(){ websocket.send("getReadings"); } We handle the messages received via WebSocket protocol on the onMessage() function. // Function that receives the message from the ESP32 with the readings function onMessage(event) { console.log(event.data); var myObj = JSON.parse(event.data); var keys = Object.keys(myObj); for (var i = 0; i < keys.length; i++){ var key = keys[i]; document.getElementById(key).innerHTML = myObj[key]; } } The server sends the readings in JSON format, for example: { temperature: 20; humidity: 50; pressure: 1023; } The onMessage() function simply goes through all the key values (temperature, humidity, and pressure) and places them in the corresponding places on the HTML page. In this case, the keys have the same name as the ids we've defined on the HTML page. So, we can simply do something like this: for (var i = 0; i < keys.length; i++){ var key = keys[i]; document.getElementById(key).innerHTML = myObj[key]; }

Code for ESP32 WebSocket Server (Sensor Readings)

Copy the following code to your Arduino IDE. /********* Rui Santos Complete instructions at https://RandomNerdTutorials.com/esp32-websocket-server-sensor/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. *********/ #include <Arduino.h> #include <WiFi.h> #include <AsyncTCP.h> #include <ESPAsyncWebServer.h> #include "SPIFFS.h" #include <Arduino_JSON.h> #include <Adafruit_BME280.h> #include <Adafruit_Sensor.h> // Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; // Create AsyncWebServer object on port 80 AsyncWebServer server(80); // Create a WebSocket object AsyncWebSocket ws("/ws"); // Json Variable to Hold Sensor Readings JSONVar readings; // Timer variables unsigned long lastTime = 0; unsigned long timerDelay = 30000; // Create a sensor object Adafruit_BME280 bme; // Init BME280 void initBME(){ if (!bme.begin(0x76)) { Serial.println("Could not find a valid BME280 sensor, check wiring!"); while (1); } } // Get Sensor Readings and return JSON object String getSensorReadings(){ readings["temperature"] = String(bme.readTemperature()); readings["humidity"] = String(bme.readHumidity()); readings["pressure"] = String(bme.readPressure()/100.0F); String jsonString = JSON.stringify(readings); return jsonString; } // Initialize SPIFFS void initSPIFFS() { if (!SPIFFS.begin(true)) { Serial.println("An error has occurred while mounting SPIFFS"); } Serial.println("SPIFFS mounted successfully"); } // Initialize WiFi void initWiFi() { WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); Serial.print("Connecting to WiFi .."); while (WiFi.status() != WL_CONNECTED) { Serial.print('.'); delay(1000); } Serial.println(WiFi.localIP()); } void notifyClients(String sensorReadings) { ws.textAll(sensorReadings); } void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) { AwsFrameInfo *info = (AwsFrameInfo*)arg; if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) { //data[len] = 0; //String message = (char*)data; // Check if the message is "getReadings" //if (strcmp((char*)data, "getReadings") == 0) { //if it is, send current sensor readings String sensorReadings = getSensorReadings(); Serial.print(sensorReadings); notifyClients(sensorReadings); //} } } void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) { switch (type) { case WS_EVT_CONNECT: Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str()); break; case WS_EVT_DISCONNECT: Serial.printf("WebSocket client #%u disconnected\n", client->id()); break; case WS_EVT_DATA: handleWebSocketMessage(arg, data, len); break; case WS_EVT_PONG: case WS_EVT_ERROR: break; } } void initWebSocket() { ws.onEvent(onEvent); server.addHandler(&ws); } void setup() { Serial.begin(115200); initBME(); initWiFi(); initSPIFFS(); initWebSocket(); // Web Server Root URL server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { request->send(SPIFFS, "/index.html", "text/html"); }); server.serveStatic("/", SPIFFS, "/"); // Start server server.begin(); } void loop() { if ((millis() - lastTime) > timerDelay) { String sensorReadings = getSensorReadings(); Serial.print(sensorReadings); notifyClients(sensorReadings); lastTime = millis(); } ws.cleanupClients(); } View raw code

How the Code Works

Continue reading to learn how the code works, or skip to the demonstration section.

Including Libraries

The Adafruit_Sensor and Adafruit_BME280 libraries are needed to interface with the BME280 sensor. #include <Adafruit_BME280.h> #include <Adafruit_Sensor.h> The WiFi, ESPAsyncWebServer and AsyncTCP libraries are used to create the web server. #include <WiFi.h> #include <AsyncTCP.h> #include <ESPAsyncWebServer.h> We'll save the HTML, CSS, and JavaScript files on the ESP32 filesystem, so we also need to include the SPIFFS.h library. #include "SPIFFS.h" To create JSON objects, we'll use the Arduino_JSON library. #include <Arduino_JSON.h>

Network Credentials

Insert your network credentials in the following variables, so that the ESP32 can connect to your local network using Wi-Fi. const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD";

AsyncWebServer and AsyncWebSocket

Create an AsyncWebServer object on port 80. AsyncWebServer server(80); The following line creates a new websocket object on /ws. // Create a WebSocket object AsyncWebSocket ws("/ws");

Timer Variables

The following variables are used to create timers in our code. In our case, we'll send sensor readings to the client via WebSocket protocol every 30000 milliseconds (30 seconds). You can change the timerDelay variable to any other value that makes sense for your project. // Timer variables unsigned long lastTime = 0; unsigned long timerDelay = 30000;

Initializing the BME280 Sensor

The following line creates an Adafruit_BME280 object to refer to the sensor called bme. // Create a sensor object Adafruit_BME280 bme; The initBME() function initializes the sensor. It will be called later in the setup(). // Init BME280 void initBME(){ if (!bme.begin(0x76)) { Serial.println("Could not find a valid BME280 sensor, check wiring!"); while (1); } }

Getting Sensor Readings (JSON String)

The getSensoreadings() function creates a JSON string with the current sensor readings. // Get Sensor Readings and return JSON object String getSensorReadings(){ readings["temperature"] = String(bme.readTemperature()); readings["humidity"] = String(bme.readHumidity()); readings["pressure"] = String(bme.readPressure()/100.0F); String jsonString = JSON.stringify(readings); return jsonString; }

Initializing the Filesystem

The initSPIFFS() function initializes SPIFFS, the ESP32 filesystem we're using in this project to save the HTML, CSS, and Javascript files. // Initialize SPIFFS void initSPIFFS() { if (!SPIFFS.begin(true)) { Serial.println("An error has occurred while mounting SPIFFS"); } Serial.println("SPIFFS mounted successfully"); }

Initializing Wi-Fi

The following function initializes Wi-Fi and connects to your network using the credentials you used previously. This function will be called later in the setup(). // Initialize WiFi void initWiFi() { WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); Serial.print("Connecting to WiFi .."); while (WiFi.status() != WL_CONNECTED) { Serial.print('.'); delay(1000); } Serial.println(WiFi.localIP()); }

Notifying all Clients Via WebSocket

The notifyClients() function notifies all clients with the current sensor readings. Calling this function is what allows us to notify changes in all clients whenever we get new sensor readings (every 30 seconds). void notifyClients(String sensorReadings) { ws.textAll(sensorReadings); }

Handling WebSocket Messages

The handleWebSocketMessage(), as the name suggests, handles what happens when the server receives a message from the client via WebSocket protocol. We've seen in the JavaScript file, that the server can receive the getReadings message. When the ESP32 receives the getReadings message, it sends the current sensor readings. void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) { AwsFrameInfo *info = (AwsFrameInfo*)arg; if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) { data[len] = 0; String message = (char*)data; //Check if the message is "getReadings" if (strcmp((char*)data, "getReadings") == 0) { if it is, send current sensor readings String sensorReadings = getSensorReadings(); Serial.print(sensorReadings); notifyClients(sensorReadings); } } }

Handling WebSocket Events

The onEvent() function handles other WebSocket events. void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) { switch (type) { case WS_EVT_CONNECT: Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str()); break; case WS_EVT_DISCONNECT: Serial.printf("WebSocket client #%u disconnected\n", client->id()); break; case WS_EVT_DATA: handleWebSocketMessage(arg, data, len); break; case WS_EVT_PONG: case WS_EVT_ERROR: break; } }

Initializing WebSocket Protocol

The initWebSocket() function initializes the WebSocket protocol. void initWebSocket() { ws.onEvent(onEvent); server.addHandler(&ws); }

setup()

In the setup(), we initialize the Serial Monitor, the BME280 sensor, Wi-Fi, the filesystem, and the WebSocket protocol by calling the functions we've created previously. Serial.begin(115200); initBME(); initWiFi(); initSPIFFS(); initWebSocket(); The following lines will serve the index.html and the other referenced static files saved on SPIFFS (style.css and script.js) when you access the web server. // Web Server Root URL server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { request->send(SPIFFS, "/index.html", "text/html"); }); server.serveStatic("/", SPIFFS, "/"); Finally, start the server. // Start server server.begin();

loop()

In the loop(), we get and send new sensor readings every 30000 milliseconds (30 seconds). void loop() { if ((millis() - lastTime) > timerDelay) { String sensorReadings = getSensorReadings(); Serial.print(sensorReadings); notifyClients(sensorReadings); lastTime = millis(); } ws.cleanupClients(); }

Upload Code and Files

After inserting your network credentials, save the code. Go to Sketch > Show Sketch Folder, and create a folder called data. Inside that folder, you should place the HTML, CSS, and JavaScript files. Upload those files to the filesystem: go to Tools > ESP32 Data Sketch Upload and wait for the files to be uploaded. Then, upload the code to your ESP32 board. Make sure you modified the code with your network credentials. When everything is successfully uploaded, open the Serial Monitor at a baud rate of 115200. Press the ESP32 EN/RST button, and it should print the ESP32 IP address.

Demonstration

Open a browser on your local network and paste the ESP32 IP address. You should get access to the web server page that displays the sensor readings. The readings update automatically on the web page every 30 seconds. You can have multiple clients on different web browser tabs or devices and it will update automatically on all clients.

Wrapping Up

In this tutorial, you've learned how to build a websocket server with the ESP32 that serves a web page to display sensor readings. The sensor readings update automatically on the web page without the need to manually refresh it. We hope you learned a lot from this tutorial. Let us know in the comments below if you successfully followed this tutorial and got the project working.

CAM: Take and Send Photos via Email using an SMTP Server

This tutorial shows how to send captured photos from the ESP32-CAM to your email account using an SMTP Server. We'll show you a simple example that takes a photo when the ESP32 boots and sends it as an attachment in an email. The last photo taken is temporarily saved in the ESP32 LittleFS filesystem. Updated 19 September 2023. To make this project work, the ESP32-CAM needs to be connected to a router with access to the internet needs to be connected to your local network. This project is compatible with any ESP32 camera board with the OV2640 camera. You just need to make sure you use the right pin assignment for the board you're using: ESP32-CAM Camera Boards: Pin and GPIOs Assignment Guide.

ESP Mail Client Library

To send emails with the ESP32-CAM, we'll use the ESP-Mail-Client library. This library allows the ESP32 to send and receive emails with or without attachments via SMTP and IMAP servers. In this project, we'll use SMTP to send an email with an attachment. The attachment is a photo taken with the ESP32-CAM. SMTP means Simple Mail Transfer Protocol and it is an internet standard for email transmission. To send emails through code, you need to know your SMTP server details. Each email provider has a different SMTP server. Installing the ESP-Mail-Client Library Before proceeding with this tutorial, you need to install the ESP-Mail-Client library. Go to Sketch > Include Library > Manage Libraries and search for ESP Mail Client. Install the ESP Mail Client library by Mobizt.

Sender Email (New Account)

We recommend creating a new email account to send the emails to your main personal email address. Do not use your main personal email to send emails via ESP32. If something goes wrong in your code or if by mistake you make too many requests, you can be banned or have your account temporarily disabled. We'll use a newly created Gmail.com account to send the emails, but you can use any other email provider. The receiver email can be your personal email without any problem.

Create a Sender Email Account

Create a new email account for sending emails with the ESP32. If you want to use a Gmail account, go to this link to create a new one.

Create an App Password

You need to create an app password so that the ESP32 is able to send emails using your Gmail account. An App Password is a 16-digit passcode that gives a less secure app or device permission to access your Google Account. Learn more about sign-in with app passwords here. An app password can only be used with accounts that have 2-step verification turned on.
    Open your Google Account. In the navigation panel, select Security. Under Signing in to Google, select 2-Step Verification > Get started. Follow the on-screen steps.
After enabling 2-step verification, you can create an app password.
    Open your Google Account. In the navigation panel, select Security. Under Signing in to Google, select App Passwords.
    In the Select app field, choose mail. For the device, select Other and give it a name, for example ESP32. Then, click on Generate. It will pop-up a window with a password that you'll use with the ESP32 or ESP8266 to send emails. Save that password (even though it says you won't need to remmeber it) because you'll need it later.
Now, you should have an app password that you'll use on the ESP32 code to send the emails. If you're using another email provider, check how to create an app password. You should be able to find the instructions with a quick google search your_email_provider + create app password.

Gmail SMTP Server Settings

If you're using a Gmail account, these are the SMTP Server details: SMTP Server: smtp.gmail.com SMTP username: Complete Gmail address SMTP password: Your Gmail password SMTP port (TLS): 587 SMTP port (SSL): 465 SMTP TLS/SSL required: yes

Outlook SMTP Server Settings

For Outlook accounts, these are the SMTP Server settings: SMTP Server: smtp.office365.com SMTP Username: Complete Outlook email address SMTP Password: Your Outlook password SMTP Port: 587 SMTP TLS/SSL Required: Yes

Live or Hotmail SMTP Server Settings

For Live or Hotmail accounts, these are the SMTP Server settings: SMTP Server: smtp.live.com SMTP Username: Complete Live/Hotmail email address SMTP Password: Your Windows Live Hotmail password SMTP Port: 587 SMTP TLS/SSL Required: Yes If you're using another email provider, you need to search for its SMTP Server settings. Now, you have everything ready to start sending emails with the ESP32-CAM.

Code ESP32-CAM Send Email

The following code takes a photo when the ESP32-CAM first boots and sends it to your email account. Before uploading the code, make sure you insert your sender email settings as well as your recipient email. /********* Rui Santos Complete instructions at https://RandomNerdTutorials.com/esp32-cam-projects-ebook/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. *********/ #include "esp_camera.h" #include "SPI.h" #include "driver/rtc_io.h" #include <ESP_Mail_Client.h> #include <FS.h> #include <WiFi.h> // REPLACE WITH YOUR NETWORK CREDENTIALS const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; // To send Email using Gmail use port 465 (SSL) and SMTP Server smtp.gmail.com // You need to create an email app password #define emailSenderAccount "[email protected]" #define emailSenderPassword "YOUR_EMAIL_APP_PASSWORD" #define smtpServer "smtp.gmail.com" #define smtpServerPort 465 #define emailSubject "ESP32-CAM Photo Captured" #define emailRecipient "[email protected]" #define CAMERA_MODEL_AI_THINKER #if defined(CAMERA_MODEL_AI_THINKER) #define PWDN_GPIO_NUM 32 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 0 #define SIOD_GPIO_NUM 26 #define SIOC_GPIO_NUM 27 #define Y9_GPIO_NUM 35 #define Y8_GPIO_NUM 34 #define Y7_GPIO_NUM 39 #define Y6_GPIO_NUM 36 #define Y5_GPIO_NUM 21 #define Y4_GPIO_NUM 19 #define Y3_GPIO_NUM 18 #define Y2_GPIO_NUM 5 #define VSYNC_GPIO_NUM 25 #define HREF_GPIO_NUM 23 #define PCLK_GPIO_NUM 22 #else #error "Camera model not selected" #endif /* The SMTP Session object used for Email sending */ SMTPSession smtp; /* Callback function to get the Email sending status */ void smtpCallback(SMTP_Status status); // Photo File Name to save in LittleFS #define FILE_PHOTO "photo.jpg" #define FILE_PHOTO_PATH "/photo.jpg" void setup() { WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector Serial.begin(115200); Serial.println(); // Connect to Wi-Fi WiFi.begin(ssid, password); Serial.print("Connecting to WiFi..."); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(); // Print ESP32 Local IP Address Serial.print("IP Address: http://"); Serial.println(WiFi.localIP()); // Init filesystem ESP_MAIL_DEFAULT_FLASH_FS.begin(); camera_config_t config; config.ledc_channel = LEDC_CHANNEL_0; config.ledc_timer = LEDC_TIMER_0; config.pin_d0 = Y2_GPIO_NUM; config.pin_d1 = Y3_GPIO_NUM; config.pin_d2 = Y4_GPIO_NUM; config.pin_d3 = Y5_GPIO_NUM; config.pin_d4 = Y6_GPIO_NUM; config.pin_d5 = Y7_GPIO_NUM; config.pin_d6 = Y8_GPIO_NUM; config.pin_d7 = Y9_GPIO_NUM; config.pin_xclk = XCLK_GPIO_NUM; config.pin_pclk = PCLK_GPIO_NUM; config.pin_vsync = VSYNC_GPIO_NUM; config.pin_href = HREF_GPIO_NUM; config.pin_sccb_sda = SIOD_GPIO_NUM; config.pin_sccb_scl = SIOC_GPIO_NUM; config.pin_pwdn = PWDN_GPIO_NUM; config.pin_reset = RESET_GPIO_NUM; config.xclk_freq_hz = 20000000; config.pixel_format = PIXFORMAT_JPEG; config.grab_mode = CAMERA_GRAB_LATEST; if(psramFound()){ config.frame_size = FRAMESIZE_UXGA; config.jpeg_quality = 10; config.fb_count = 1; } else { config.frame_size = FRAMESIZE_SVGA; config.jpeg_quality = 12; config.fb_count = 1; } // Initialize camera esp_err_t err = esp_camera_init(&config); if (err != ESP_OK) { Serial.printf("Camera init failed with error 0x%x", err); return; } capturePhotoSaveLittleFS(); sendPhoto(); } void loop() { } // Capture Photo and Save it to LittleFS void capturePhotoSaveLittleFS( void ) { // Dispose first pictures because of bad quality camera_fb_t* fb = NULL; // Skip first 3 frames (increase/decrease number as needed). for (int i = 0; i < 3; i++) { fb = esp_camera_fb_get(); esp_camera_fb_return(fb); fb = NULL; } // Take a new photo fb = NULL; fb = esp_camera_fb_get(); if(!fb) { Serial.println("Camera capture failed"); delay(1000); ESP.restart(); } // Photo file name Serial.printf("Picture file name: %s\n", FILE_PHOTO_PATH); File file = LittleFS.open(FILE_PHOTO_PATH, FILE_WRITE); // Insert the data in the photo file if (!file) { Serial.println("Failed to open file in writing mode"); } else { file.write(fb->buf, fb->len); // payload (image), payload length Serial.print("The picture has been saved in "); Serial.print(FILE_PHOTO_PATH); Serial.print(" - Size: "); Serial.print(fb->len); Serial.println(" bytes"); } // Close the file file.close(); esp_camera_fb_return(fb); } void sendPhoto( void ) { /** Enable the debug via Serial port * none debug or 0 * basic debug or 1 */ smtp.debug(1); /* Set the callback function to get the sending results */ smtp.callback(smtpCallback); /* Declare the session config data */ Session_Config config; /*Set the NTP config time For times east of the Prime Meridian use 0-12 For times west of the Prime Meridian add 12 to the offset. Ex. American/Denver GMT would be -6. 6 + 12 = 18 See https://en.wikipedia.org/wiki/Time_zone for a list of the GMT/UTC timezone offsets */ config.time.ntp_server = F("pool.ntp.org,time.nist.gov"); config.time.gmt_offset = 0; config.time.day_light_offset = 1; /* Set the session config */ config.server.host_name = smtpServer; config.server.port = smtpServerPort; config.login.email = emailSenderAccount; config.login.password = emailSenderPassword; config.login.user_domain = ""; /* Declare the message class */ SMTP_Message message; /* Enable the chunked data transfer with pipelining for large message if server supported */ message.enable.chunking = true; /* Set the message headers */ message.sender.name = "ESP32-CAM"; message.sender.email = emailSenderAccount; message.subject = emailSubject; message.addRecipient("Sara", emailRecipient); String htmlMsg = "<h2>Photo captured with ESP32-CAM and attached in this email.</h2>"; message.html.content = htmlMsg.c_str(); message.html.charSet = "utf-8"; message.html.transfer_encoding = Content_Transfer_Encoding::enc_qp; message.priority = esp_mail_smtp_priority::esp_mail_smtp_priority_normal; message.response.notify = esp_mail_smtp_notify_success | esp_mail_smtp_notify_failure | esp_mail_smtp_notify_delay; /* The attachment data item */ SMTP_Attachment att; /** Set the attachment info e.g. * file name, MIME type, file path, file storage type, * transfer encoding and content encoding */ att.descr.filename = FILE_PHOTO; att.descr.mime = "image/png"; att.file.path = FILE_PHOTO_PATH; att.file.storage_type = esp_mail_file_storage_type_flash; att.descr.transfer_encoding = Content_Transfer_Encoding::enc_base64; /* Add attachment to the message */ message.addAttachment(att); /* Connect to server with the session config */ if (!smtp.connect(&config)) return; /* Start sending the Email and close the session */ if (!MailClient.sendMail(&smtp, &message, true)) Serial.println("Error sending Email, " + smtp.errorReason()); } // Callback function to get the Email sending status void smtpCallback(SMTP_Status status){ /* Print the current status */ Serial.println(status.info()); /* Print the sending result */ if (status.success()) { Serial.println("----------------"); Serial.printf("Message sent success: %d\n", status.completedCount()); Serial.printf("Message sent failled: %d\n", status.failedCount()); Serial.println("----------------\n"); struct tm dt; for (size_t i = 0; i < smtp.sendingResult.size(); i++){ /* Get the result item */ SMTP_Result result = smtp.sendingResult.getItem(i); time_t ts = (time_t)result.timestamp; localtime_r(&ts, &dt); ESP_MAIL_PRINTF("Message No: %d\n", i + 1); ESP_MAIL_PRINTF("Status: %s\n", result.completed ? "success" : "failed"); ESP_MAIL_PRINTF("Date/Time: %d/%d/%d %d:%d:%d\n", dt.tm_year + 1900, dt.tm_mon + 1, dt.tm_mday, dt.tm_hour, dt.tm_min, dt.tm_sec); ESP_MAIL_PRINTF("Recipient: %s\n", result.recipients.c_str()); ESP_MAIL_PRINTF("Subject: %s\n", result.subject.c_str()); } Serial.println("----------------\n"); // You need to clear sending result as the memory usage will grow up. smtp.sendingResult.clear(); } } View raw code

How the Code Works

Continue reading to learn how the code works, or skip to the Demonstration section. Don't forget to insert your network credentials and email settings in the code. Also, if you're using a camera model other than an ESP32-CAM AI-Thinker, don't forget to change the pin assignment.

Importing Libraries

Import the required libraries. The ESP3_Mail_Client.h is used to send emails, the FS.h is used to access and save files to LittleFS and the WiFi.h library is used to initialize Wi-Fi and connect your ESP32-CAM to your local network. #include "esp_camera.h" #include "SPI.h" #include "driver/rtc_io.h" #include <ESP_Mail_Client.h> #include <FS.h> #include <WiFi.h>

Network Credentials

Insert your network credentials in the following variables: const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Email Settings

Type the email sender account on the emailSenderAccount variable and its password on the emailSenderPassword variable. #define emailSenderAccount "[email protected]" #define emailSenderPassword "SENDER_ACCOUNT_PASSWORD" Insert the recipient's email. This is the email that will receive the emails sent by the ESP32: #define emailRecipient "[email protected]" Insert your email provider SMTP settings on the following lines. We're using the settings for a Gmail account. If you're using a different email provider, replace with the corresponding SMTP settings. #define smtpServer "smtp.gmail.com" #define smtpServerPort 465 Write the email subject on the emailSubject variable. #define emailSubject "ESP32-CAM Photo Captured" Create a STMPSession object called smtp that contains the data to send via email and all the other configurations. /* The SMTP Session object used for Email sending */ SMTPSession smtp; The photo taken with the ESP32 camera will be temporarily saved in LittleFS under the name photo.jpg on the root directory. #define FILE_PHOTO "photo.jpg" #define FILE_PHOTO_PATH "/photo.jpg"

ESP32 Camera Pins

Define the pins used by your camera model. We're using the ESP32-CAM AI-Thinker. #define PWDN_GPIO_NUM 32 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 0 #define SIOD_GPIO_NUM 26 #define SIOC_GPIO_NUM 27 #define Y9_GPIO_NUM 35 #define Y8_GPIO_NUM 34 #define Y7_GPIO_NUM 39 #define Y6_GPIO_NUM 36 #define Y5_GPIO_NUM 21 #define Y4_GPIO_NUM 19 #define Y3_GPIO_NUM 18 #define Y2_GPIO_NUM 5 #define VSYNC_GPIO_NUM 25 #define HREF_GPIO_NUM 23 #define PCLK_GPIO_NUM 22

setup()

In the setup(), connect the ESP32 to Wi-Fi. // Connect to Wi-Fi WiFi.begin(ssid, password); Serial.print("Connecting to WiFi..."); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(); Print the ESP32-CAM IP address: // Print ESP32 Local IP Address Serial.print("IP Address: http://"); Serial.println(WiFi.localIP()); In the setup(), you initialize the filesystem using the ESP Mail Client library method. The default filesystem set in the library for the ESP32 is LittleFS (you can change the default in the library file ESP_Mail_FS.h). // Init filesystem ESP_MAIL_DEFAULT_FLASH_FS.begin(); The following lines configure the camera and set the camera settings: camera_config_t config; config.ledc_channel = LEDC_CHANNEL_0; config.ledc_timer = LEDC_TIMER_0; config.pin_d0 = Y2_GPIO_NUM; config.pin_d1 = Y3_GPIO_NUM; config.pin_d2 = Y4_GPIO_NUM; config.pin_d3 = Y5_GPIO_NUM; config.pin_d4 = Y6_GPIO_NUM; config.pin_d5 = Y7_GPIO_NUM; config.pin_d6 = Y8_GPIO_NUM; config.pin_d7 = Y9_GPIO_NUM; config.pin_xclk = XCLK_GPIO_NUM; config.pin_pclk = PCLK_GPIO_NUM; config.pin_vsync = VSYNC_GPIO_NUM; config.pin_href = HREF_GPIO_NUM; config.pin_sccb_sda = SIOD_GPIO_NUM; config.pin_sccb_scl = SIOC_GPIO_NUM; config.pin_pwdn = PWDN_GPIO_NUM; config.pin_reset = RESET_GPIO_NUM; config.xclk_freq_hz = 20000000; config.pixel_format = PIXFORMAT_JPEG; config.grab_mode = CAMERA_GRAB_LATEST; if(psramFound()){ config.frame_size = FRAMESIZE_UXGA; config.jpeg_quality = 10; config.fb_count = 2; } else { config.frame_size = FRAMESIZE_SVGA; config.jpeg_quality = 12; config.fb_count = 1; } Initialize the camera. // Initialize camera esp_err_t err = esp_camera_init(&config); if (err != ESP_OK) { Serial.printf("Camera init failed with error 0x%x", err); return; } After initializing the camera, call the capturePhotoSaveLittleFS() and the sendPhoto() functions. These functions are defined at the end of the code.

capturePhotoSaveLittleFS() function

The capturePhotoSaveLittleFS() function captures a photo and saves it in the ESP32 LittleFS. In the following lines, you take a photo and save it in the framebuffer FB. Note: sometimes, the first pictures taken with the ESP32-CAM are not good because the sensor has not adjusted the white balance yet. So, to make sure we get a good picture, we discard the first three pictures (that number may vary depending on your board, so adjust accordingly). //Dispose first pictures because of bad quality camera_fb_t* fb = NULL; // Skip first 3 frames (increase/decrease number as needed). for (int i = 0; i < 3; i++) { fb = esp_camera_fb_get(); esp_camera_fb_return(fb); fb = NULL; } // Take a new photo fb = NULL; fb = esp_camera_fb_get(); if(!fb) { Serial.println("Camera capture failed"); delay(1000); ESP.restart(); } Then, create a new file in LitteFS where the photo will be saved. Serial.printf("Picture file name: %s\n", FILE_PHOTO_PATH); File file = LittleFS.open(FILE_PHOTO_PATH, FILE_WRITE); Check if the file was successfully created. If not, print an error message. if (!file) { Serial.println("Failed to open file in writing mode"); } If a new file was successfully created, copy the image from the buffer to that newly created file. file.write(fb->buf, fb->len); // payload (image), payload length Serial.print("The picture has been saved in "); Serial.print(FILE_PHOTO_PATH); Serial.print(" - Size: "); Serial.print(fb->len); Serial.println(" bytes"); Close the file and clear the buffer for future use. file.close(); esp_camera_fb_return(fb);

sendPhoto() function

After having the photo successfully saved in LittleFS, we'll send it via email by calling the sendPhoto() function. Let's take a look at that function. void sendPhoto( void ) { Enable debugging via serial port for the email status: smtp.debug(1); Set the callback function to get the sending results: smtp.callback(smtpCallback); Declare an email session: Session_Config config; Configure the time so that the email is timestamped correctly. You may need to adjust the gmt_offset variable depending on your location. /*Set the NTP config time For times east of the Prime Meridian use 0-12 For times west of the Prime Meridian add 12 to the offset. Ex. American/Denver GMT would be -6. 6 + 12 = 18 See https://en.wikipedia.org/wiki/Time_zone for a list of the GMT/UTC timezone offsets */ config.time.ntp_server = F("pool.ntp.org,time.nist.gov"); config.time.gmt_offset = 0; config.time.day_light_offset = 1; Set the server host, port, sender email, and password for this email session: /* Set the session config */ config.server.host_name = smtpServer; config.server.port = smtpServerPort; config.login.email = emailSenderAccount; config.login.password = emailSenderPassword; config.login.user_domain = ""; Declare a SMTP_Message class: SMTP_Message message; Set the message headerssender name, email sender, subject, and recipient (you can change the recipient name): /* Set the message headers */ message.sender.name = "ESP32-CAM"; message.sender.email = emailSenderAccount; message.subject = emailSubject; message.addRecipient("Sara", emailRecipient); In the following lines, set the content of the message in the htmlMsg variable: String htmlMsg = "<h2>Photo captured with ESP32-CAM and attached in this email.</h2>"; message.html.content = htmlMsg.c_str(); message.html.charSet = "utf-8"; message.html.transfer_encoding = Content_Transfer_Encoding::enc_qp; Now, we need to take care of the attachments. Create an attachment: SMTP_Attachment att; Set the attachment info: document name, mime type, file path, and where the content is saved (in our case it is saved in flash (esp_mail_file_storage_type_flash)): att.descr.filename = FILE_PHOTO; att.descr.mime = "image/png"; att.file.path = FILE_PHOTO_PATH; att.file.storage_type = esp_mail_file_storage_type_flash; att.descr.transfer_encoding = Content_Transfer_Encoding::enc_base64; Add the attachment to the message: /* Add attachment to the message */ message.addAttachment(att); Connect to the SMTP server: /* Connect to server with the session config */ if (!smtp.connect(&config)) return; Finally, the following lines send the message: /* Start sending the Email and close the session */ if (!MailClient.sendMail(&smtp, &message, true)) Serial.println("Error sending Email, " + smtp.errorReason()); In this example, the email is sent once when the ESP32 boots, that's why the loop() is empty. void loop() { } To send a new email, you just need to reset your board (press the on-board RESET button).

Demonstration

After making the necessary changes to the code: camera pinout, sender's email address, sender's email password, recipient's email address, and network credentials, you can upload the code to your board. If you don't know how to upload code to the ESP32-CAM, read the following post: How to Program / Upload Code to ESP32-CAM AI-Thinker (Arduino IDE) After uploading, open the Serial Monitor and press the ESP32-CAM RESET button. The ESP32 should connect to Wi-Fi, take a photo, save it in LittleFS, connect to the SMTP server, and send the email as shown below. After a few seconds, you should have a new email from the ESP32-CAM in your inbox. As you can see in the image below, the sender's email name is ESP32-CAM as we've defined in the code, and the subject ESP32-CAM Photo Captured. Open the email and you should see a photo captured by the ESP32-CAM. You can open or download the photo to see it in full size.

Wrapping Up

In this tutorial, you've learned how to send emails with photos taken with the ESP32-CAM. The example presented is as simple as possible: it takes a photo and sends it via email when the ESP32-CAM first boots. To send another email, you need to reset the board. This project doesn't have a practical application, but it is useful to understand how to send an email with an attachment. After this, it should be fairly easy to include this feature in your own ESP32-CAM projects.

Send Emails using an SMTP Server: HTML, Text, and Attachments (Arduino IDE)

Learn how to send emails with the ESP32 using an SMTP Server. We'll show you how to send a simple email with HTML or raw text and how to send attachments like images and files (.txt). The ESP32 board will be programmed using Arduino IDE. Updated 24 June 2023 In this tutorial, we cover the following topics: ESP32 Send Email (HTML and raw text) ESP32 Send Emails with Attachments (image and .txt file) We have a similar tutorial for the ESP8266 board: ESP8266 NodeMCU Send Emails using an SMTP Server: HTML, Text, and Attachments (Arduino)

Introducing SMTP Servers

SMTP means Simple Mail Transfer Protocol and it is an internet standard for email transmission. To send emails using an ESP32, you need to connect it to an SMTP Server.

ESP Mail Client Library

To send emails with the ESP32, we'll use the ESP-Mail-Client library. This library allows the ESP32 to send and receive emails with or without attachments via SMTP and IMAP servers. In this tutorial, we'll use SMTP to send an email with and without attachments. As an example, we'll send an image (.png) and a text (.txt) file. The files sent via email can be saved in the ESP32 Filesystem (SPIFFS, LittleFS, or FatFs) or a microSD card (not covered in this tutorial). Installing the ESP-Mail-Client Library Before proceeding with this tutorial, you need to install the ESP-Mail-Client library. Go to Sketch > Include Library > Manage Libraries and search for ESP Mail Client. Install the ESP Mail Client library by Mobizt.

Sender Email (New Account)

We recommend creating a new email account to send the emails to your main personal email address. Do not use your main personal email to send emails via ESP32. If something goes wrong in your code or if by mistake you make too many requests, you can be banned or have your account temporarily disabled. We'll use a newly created Gmail.com account to send the emails, but you can use any other email provider. The receiver email can be your personal email without any problem.

Create a Sender Email Account

Create a new email account for sending emails with the ESP32. If you want to use a Gmail account, go to this link to create a new one.

Create an App Password

You need to create an app password so that the ESP32 is able to send emails using your Gmail account. An App Password is a 16-digit passcode that gives a less secure app or device permission to access your Google Account. Learn more about sign-in with app passwords here. An app password can only be used with accounts that have 2-step verification turned on.
    Open your Google Account. In the navigation panel, select Security. Under Signing in to Google, select 2-Step Verification > Get started. Follow the on-screen steps.
After enabling 2-step verification, you can create an app password.
    Open your Google Account. In the navigation panel, select Security. Under Signing in to Google, select App Passwords.
    In the Select app field, choose mail. For the device, select Other and give it a name, for example ESP32. Then, click on Generate. It will pop-up a window with a password that you'll use with the ESP32 or ESP8266 to send emails. Save that password (even though it says you won't need to remember it) because you'll need it later.
Now, you should have an app password that you'll use on the ESP32 code to send the emails. If you're using another email provider, check how to create an app password. You should be able to find the instructions with a quick google search your_email_provider + create app password.

Gmail SMTP Server Settings

If you're using a Gmail account, these are the SMTP Server details: SMTP Server: smtp.gmail.com SMTP username: Complete Gmail address SMTP password: Your Gmail password SMTP port (TLS): 587 SMTP port (SSL): 465 SMTP TLS/SSL required: yes

Outlook SMTP Server Settings

For Outlook accounts, these are the SMTP Server settings: SMTP Server: smtp.office365.com SMTP Username: Complete Outlook email address SMTP Password: Your Outlook password SMTP Port: 587 SMTP TLS/SSL Required: Yes

Live or Hotmail SMTP Server Settings

For Live or Hotmail accounts, these are the SMTP Server settings: SMTP Server: smtp.live.com SMTP Username: Complete Live/Hotmail email address SMTP Password: Your Windows Live Hotmail password SMTP Port: 587 SMTP TLS/SSL Required: Yes If you're using another email provider, you need to search for its SMTP Server settings. Now, you have everything ready to start sending emails with your ESP32.

Send an Email with HTML or Raw Text with ESP32 (Arduino IDE)

The following code sends an email via SMTP Server with HTML or raw text. For demonstration purposes, the ESP32 sends an email once when it boots. Then, you should be able to modify the code and integrate it into your own projects. Don't upload the code yet, you need to make some modifications to make it work for you. /* Rui Santos Complete project details at: - ESP32: https://RandomNerdTutorials.com/esp32-send-email-smtp-server-arduino-ide/ - ESP8266: https://RandomNerdTutorials.com/esp8266-nodemcu-send-email-smtp-server-arduino/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Example adapted from: https://github.com/mobizt/ESP-Mail-Client */ #include <Arduino.h> #if defined(ESP32) #include <WiFi.h> #elif defined(ESP8266) #include <ESP8266WiFi.h> #endif #include <ESP_Mail_Client.h> #define WIFI_SSID "REPLACE_WITH_YOUR_SSID" #define WIFI_PASSWORD "REPLACE_WITH_YOUR_PASSWORD" /** The smtp host name e.g. smtp.gmail.com for GMail or smtp.office365.com for Outlook or smtp.mail.yahoo.com */ #define SMTP_HOST "smtp.gmail.com" #define SMTP_PORT 465 /* The sign in credentials */ #define AUTHOR_EMAIL "[email protected]" #define AUTHOR_PASSWORD "YOUR_EMAIL_APP_PASS" /* Recipient's email*/ #define RECIPIENT_EMAIL "[email protected]" /* Declare the global used SMTPSession object for SMTP transport */ SMTPSession smtp; /* Callback function to get the Email sending status */ void smtpCallback(SMTP_Status status); void setup(){ Serial.begin(115200); Serial.println(); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); Serial.print("Connecting to Wi-Fi"); while (WiFi.status() != WL_CONNECTED){ Serial.print("."); delay(300); } Serial.println(); Serial.print("Connected with IP: "); Serial.println(WiFi.localIP()); Serial.println(); /* Set the network reconnection option */ MailClient.networkReconnect(true); /** Enable the debug via Serial port * 0 for no debugging * 1 for basic level debugging * * Debug port can be changed via ESP_MAIL_DEFAULT_DEBUG_PORT in ESP_Mail_FS.h */ smtp.debug(1); /* Set the callback function to get the sending results */ smtp.callback(smtpCallback); /* Declare the Session_Config for user defined session credentials */ Session_Config config; /* Set the session config */ config.server.host_name = SMTP_HOST; config.server.port = SMTP_PORT; config.login.email = AUTHOR_EMAIL; config.login.password = AUTHOR_PASSWORD; config.login.user_domain = ""; /* Set the NTP config time For times east of the Prime Meridian use 0-12 For times west of the Prime Meridian add 12 to the offset. Ex. American/Denver GMT would be -6. 6 + 12 = 18 See https://en.wikipedia.org/wiki/Time_zone for a list of the GMT/UTC timezone offsets */ config.time.ntp_server = F("pool.ntp.org,time.nist.gov"); config.time.gmt_offset = 3; config.time.day_light_offset = 0; /* Declare the message class */ SMTP_Message message; /* Set the message headers */ message.sender.name = F("ESP"); message.sender.email = AUTHOR_EMAIL; message.subject = F("ESP Test Email"); message.addRecipient(F("Sara"), RECIPIENT_EMAIL); /*Send HTML message*/ /*String htmlMsg = "<div style=\"color:#2f4468;\"><h1>Hello World!</h2><p>- Sent from ESP board</p></div>"; message.html.content = htmlMsg.c_str(); message.html.content = htmlMsg.c_str(); message.text.charSet = "us-ascii"; message.html.transfer_encoding = Content_Transfer_Encoding::enc_7bit;*/ //Send raw text message String textMsg = "Hello World! - Sent from ESP board"; message.text.content = textMsg.c_str(); message.text.charSet = "us-ascii"; message.text.transfer_encoding = Content_Transfer_Encoding::enc_7bit; message.priority = esp_mail_smtp_priority::esp_mail_smtp_priority_low; message.response.notify = esp_mail_smtp_notify_success | esp_mail_smtp_notify_failure | esp_mail_smtp_notify_delay; /* Connect to the server */ if (!smtp.connect(&config)){ ESP_MAIL_PRINTF("Connection error, Status Code: %d, Error Code: %d, Reason: %s", smtp.statusCode(), smtp.errorCode(), smtp.errorReason().c_str()); return; } if (!smtp.isLoggedIn()){ Serial.println("\nNot yet logged in."); } else{ if (smtp.isAuthenticated()) Serial.println("\nSuccessfully logged in."); else Serial.println("\nConnected with no Auth."); } /* Start sending Email and close the session */ if (!MailClient.sendMail(&smtp, &message)) ESP_MAIL_PRINTF("Error, Status Code: %d, Error Code: %d, Reason: %s", smtp.statusCode(), smtp.errorCode(), smtp.errorReason().c_str()); } void loop(){ } /* Callback function to get the Email sending status */ void smtpCallback(SMTP_Status status){ /* Print the current status */ Serial.println(status.info()); /* Print the sending result */ if (status.success()){ // ESP_MAIL_PRINTF used in the examples is for format printing via debug Serial port // that works for all supported Arduino platform SDKs e.g. AVR, SAMD, ESP32 and ESP8266. // In ESP8266 and ESP32, you can use Serial.printf directly. Serial.println("----------------"); ESP_MAIL_PRINTF("Message sent success: %d\n", status.completedCount()); ESP_MAIL_PRINTF("Message sent failed: %d\n", status.failedCount()); Serial.println("----------------\n"); for (size_t i = 0; i < smtp.sendingResult.size(); i++) { /* Get the result item */ SMTP_Result result = smtp.sendingResult.getItem(i); // In case, ESP32, ESP8266 and SAMD device, the timestamp get from result.timestamp should be valid if // your device time was synched with NTP server. // Other devices may show invalid timestamp as the device time was not set i.e. it will show Jan 1, 1970. // You can call smtp.setSystemTime(xxx) to set device time manually. Where xxx is timestamp (seconds since Jan 1, 1970) ESP_MAIL_PRINTF("Message No: %d\n", i + 1); ESP_MAIL_PRINTF("Status: %s\n", result.completed ? "success" : "failed"); ESP_MAIL_PRINTF("Date/Time: %s\n", MailClient.Time.getDateTimeString(result.timestamp, "%B %d, %Y %H:%M:%S").c_str()); ESP_MAIL_PRINTF("Recipient: %s\n", result.recipients.c_str()); ESP_MAIL_PRINTF("Subject: %s\n", result.subject.c_str()); } Serial.println("----------------\n"); // You need to clear sending result as the memory usage will grow up. smtp.sendingResult.clear(); } } View raw code You need to insert your network credentials as well as set the sender email, SMTP Server details, recipient, and message.

How the Code Works

This code is adapted from an example provided by the library. The example is well commented so that you understand what each line of code does. Let's just take a look at the relevant parts that you need or may need to change. First, insert your network credentials in the following lines: #define WIFI_SSID "REPLACE_WITH_YOUR_SSID" #define WIFI_PASSWORD "REPLACE_WITH_YOUR_PASSWORD" Insert your SMTP server settings. If you're using a Gmail account to send the emails, these are the settings: #define SMTP_HOST "smtp.gmail.com" #define SMTP_PORT 465 Insert the sender email login credentials (complete email and app password you created previously). #define AUTHOR_EMAIL "[email protected]" #define AUTHOR_PASSWORD "YOUR_EMAIL_PASS" Insert the recipient email: #define RECIPIENT_EMAIL "[email protected]" You may need to adjust the gmt_offset variable depending on your location so that the email is timestamped with the right time. config.time.ntp_server = F("pool.ntp.org,time.nist.gov"); config.time.gmt_offset = 3; config.time.day_light_offset = 0; Set the message headers in the following lines in the setup()sender name, sender email, email subject, and the recipient name and email: /* Set the message headers */ message.sender.name = F("ESP"); message.sender.email = AUTHOR_EMAIL; message.subject = F("ESP Test Email"); message.addRecipient(F("Sara"), RECIPIENT_EMAIL); In the following lines, set the content of the message (raw text) in the textMsg variable: //Send raw text message String textMsg = "Hello World - Sent from ESP board"; message.text.content = textMsg.c_str(); message.text.charSet = "us-ascii"; message.text.transfer_encoding = Content_Transfer_Encoding::enc_7bit; If you want to send HTML text instead, uncomment the following lines you should insert your HTML text in the htmlMsg variable. /*Send HTML message*/ /*String htmlMsg = "<div style=\"color:#2f4468;\"><h1>Hello World!</h2><p>- Sent from ESP board</p></div>"; message.html.content = htmlMsg.c_str(); message.html.content = htmlMsg.c_str(); message.text.charSet = "us-ascii"; message.html.transfer_encoding = Content_Transfer_Encoding::enc_7bit;*/ Finally, the following lines send the message: if (!MailClient.sendMail(&smtp, &message)) Serial.println("Error sending Email, " + smtp.errorReason());

Demonstration

Upload the code to your ESP32. After uploading, open the Serial Monitor at a baud rate of 115200. Press the ESP32 Reset button. If everything went as expected you should get a similar message in the Serial Monitor. Check your email account. You should have received an email from your ESP32 board. If you set the option to send a message with HTML text, this is what the message looks like: If you've enabled the raw text message, this is the email that you should receive.

Send Attachments via Email with ESP32 (Arduino IDE)

In this section, we'll show you how to send attachments in your emails sent by the ESP32. We'll show you how to send .txt files or pictures. This can be useful to send a .txt file with sensor readings from the past few hours or to send a photo captured by an ESP32-CAM. For this tutorial, the files to be sent should be saved on the ESP32 filesystem (LittleFS).

Upload files to LittleFS

To send files via email, these should be saved on the ESP32 filesystem or on a microSD card. We'll upload a picture and a .txt file to the ESP32 LittleFS filesystem using the ESP32 Filesystem Uploader plugin for Arduino IDE. Follow the next tutorial to install the plugin if you don't have it installed yet: Install ESP32 Filesystem Uploader in Arduino IDE (LittleFS and SPIFFS support) Create a new Arduino sketch and save it. Go to Sketch > Show Sketch folder. Inside the Arduino sketch folder, create a folder called data. Move a .jpg file and .txt file to your data folder. Alternatively, you can click here to download the project folder. Note: with the default code, your files must be named image.png and text_file.txt. Alternatively, you can modify the code to import files with a different name. We'll be sending these files: Your folder structure should look as follows (download project folder): After moving the files to the data folder, in your Arduino IDE, go to Tools > ESP32 Sketch Data Upload. Then, select LittleFS and wait for the files to be uploaded. You should get a success message on the debugging window. If the files were successfully uploaded, move on to the next section. Note: if you start seeing many dots .____..____ being printed on the debugging window, you need to hold the ESP32 on-board BOOT button for the files to be uploaded.

Code

The following code sends an email with a .txt file and a picture attached. Before uploading the code, make sure you insert your sender email settings and your recipient email. /* Rui Santos Complete project details at: - ESP32: https://RandomNerdTutorials.com/esp32-send-email-smtp-server-arduino-ide/ - ESP8266: https://RandomNerdTutorials.com/esp8266-nodemcu-send-email-smtp-server-arduino/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Example adapted K. Suwatchai (Mobizt): https://github.com/mobizt/ESP-Mail-Client Copyright (c) 2021 mobizt */ // To use send Email for Gmail to port 465 (SSL), less secure app option should be enabled. https://myaccount.google.com/lesssecureapps?pli=1 #include <Arduino.h> #if defined(ESP32) #include <WiFi.h> #elif defined(ESP8266) #include <ESP8266WiFi.h> #endif #include <ESP_Mail_Client.h> #define WIFI_SSID "REPLACE_WITH_YOUR_SSID" #define WIFI_PASSWORD "REPLACE_WITH_YOUR_PASSWORD" #define SMTP_HOST "smtp.gmail.com" /** The smtp port e.g. * 25 or esp_mail_smtp_port_25 * 465 or esp_mail_smtp_port_465 * 587 or esp_mail_smtp_port_587 */ #define SMTP_PORT 465 /* The sign in credentials */ #define AUTHOR_EMAIL "[email protected]" #define AUTHOR_PASSWORD "YOUR_EMAIL_APP_PASS" /* Recipient's email*/ #define RECIPIENT_EMAIL "[email protected]" /* The SMTP Session object used for Email sending */ SMTPSession smtp; /* Callback function to get the Email sending status */ void smtpCallback(SMTP_Status status); void setup(){ Serial.begin(115200); Serial.println(); Serial.print("Connecting to AP"); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() != WL_CONNECTED){ Serial.print("."); delay(200); } Serial.println(""); Serial.println("WiFi connected."); Serial.println("IP address: "); Serial.println(WiFi.localIP()); Serial.println(); // Init filesystem ESP_MAIL_DEFAULT_FLASH_FS.begin(); /** Enable the debug via Serial port * none debug or 0 * basic debug or 1 */ smtp.debug(1); /* Set the callback function to get the sending results */ smtp.callback(smtpCallback); /* Declare the Session_Config for user defined session credentials */ Session_Config config; /* Set the session config */ config.server.host_name = SMTP_HOST; config.server.port = SMTP_PORT; config.login.email = AUTHOR_EMAIL; config.login.password = AUTHOR_PASSWORD; config.login.user_domain = "mydomain.net"; /* Set the NTP config time For times east of the Prime Meridian use 0-12 For times west of the Prime Meridian add 12 to the offset. Ex. American/Denver GMT would be -6. 6 + 12 = 18 See https://en.wikipedia.org/wiki/Time_zone for a list of the GMT/UTC timezone offsets */ config.time.ntp_server = F("pool.ntp.org,time.nist.gov"); config.time.gmt_offset = 3; config.time.day_light_offset = 0; /* Declare the message class */ SMTP_Message message; /* Enable the chunked data transfer with pipelining for large message if server supported */ message.enable.chunking = true; /* Set the message headers */ message.sender.name = "ESP Mail"; message.sender.email = AUTHOR_EMAIL; message.subject = F("Test sending Email with attachments and inline images from Flash"); message.addRecipient(F("Sara"), RECIPIENT_EMAIL); /** Two alternative content versions are sending in this example e.g. plain text and html */ String htmlMsg = "This message contains attachments: image and text file."; message.html.content = htmlMsg.c_str(); message.html.charSet = "utf-8"; message.html.transfer_encoding = Content_Transfer_Encoding::enc_qp; message.priority = esp_mail_smtp_priority::esp_mail_smtp_priority_normal; message.response.notify = esp_mail_smtp_notify_success | esp_mail_smtp_notify_failure | esp_mail_smtp_notify_delay; /* The attachment data item */ SMTP_Attachment att; /** Set the attachment info e.g. * file name, MIME type, file path, file storage type, * transfer encoding and content encoding */ att.descr.filename = "image.png"; att.descr.mime = "image/png"; //binary data att.file.path = "/image.png"; att.file.storage_type = esp_mail_file_storage_type_flash; att.descr.transfer_encoding = Content_Transfer_Encoding::enc_base64; /* Add attachment to the message */ message.addAttachment(att); message.resetAttachItem(att); att.descr.filename = "text_file.txt"; att.descr.mime = "text/plain"; att.file.path = "/text_file.txt"; att.file.storage_type = esp_mail_file_storage_type_flash; att.descr.transfer_encoding = Content_Transfer_Encoding::enc_base64; /* Add attachment to the message */ message.addAttachment(att); /* Connect to server with the session config */ if (!smtp.connect(&config)){ ESP_MAIL_PRINTF("Connection error, Status Code: %d, Error Code: %d, Reason: %s", smtp.statusCode(), smtp.errorCode(), smtp.errorReason().c_str()); return; } if (!smtp.isLoggedIn()){ Serial.println("\nNot yet logged in."); } else{ if (smtp.isAuthenticated()) Serial.println("\nSuccessfully logged in."); else Serial.println("\nConnected with no Auth."); } /* Start sending the Email and close the session */ if (!MailClient.sendMail(&smtp, &message, true)) Serial.println("Error sending Email, " + smtp.errorReason()); } void loop(){ } /* Callback function to get the Email sending status */ void smtpCallback(SMTP_Status status){ /* Print the current status */ Serial.println(status.info()); /* Print the sending result */ if (status.success()){ Serial.println("----------------"); ESP_MAIL_PRINTF("Message sent success: %d\n", status.completedCount()); ESP_MAIL_PRINTF("Message sent failled: %d\n", status.failedCount()); Serial.println("----------------\n"); struct tm dt; for (size_t i = 0; i < smtp.sendingResult.size(); i++){ /* Get the result item */ SMTP_Result result = smtp.sendingResult.getItem(i); time_t ts = (time_t)result.timestamp; localtime_r(&ts, &dt); ESP_MAIL_PRINTF("Message No: %d\n", i + 1); ESP_MAIL_PRINTF("Status: %s\n", result.completed ? "success" : "failed"); ESP_MAIL_PRINTF("Date/Time: %d/%d/%d %d:%d:%d\n", dt.tm_year + 1900, dt.tm_mon + 1, dt.tm_mday, dt.tm_hour, dt.tm_min, dt.tm_sec); ESP_MAIL_PRINTF("Recipient: %s\n", result.recipients.c_str()); ESP_MAIL_PRINTF("Subject: %s\n", result.subject.c_str()); } Serial.println("----------------\n"); // You need to clear sending result as the memory usage will grow up. smtp.sendingResult.clear(); } } View raw code

How the code works

This code is very similar to the previous one, so we'll just take a look at the relevant parts to send attachments. In the setup(), you initialize the filesystem using the ESP Mail Client library method. The default filesystem set in the library for the ESP32 is LittleFS (you can change the default in the library file ESP_Mail_FS.h). // Init filesystem ESP_MAIL_DEFAULT_FLASH_FS.begin(); You need to create an attachment as follows: /* The attachment data item */ SMTP_Attachment att; Then, add the attachment details: filename, MIME type, file path, file storage type, and transfer encoding. In the following lines, we're sending the image file. att.descr.filename = "image.png"; att.descr.mime = "image/png"; att.file.path = "/image.png"; att.file.storage_type = esp_mail_file_storage_type_flash; att.descr.transfer_encoding = Content_Transfer_Encoding::enc_base64; Finally, add the attachment to the message: message.addAttachment(att); If you want to send more attachments, you need to call the following line before adding the next attachment: message.resetAttachItem(att); Then, enter the details of the other attachment (text file): att.descr.filename = "text_file.txt"; att.descr.mime = "text/plain"; att.file.path = "/text_file.txt"; att.file.storage_type = esp_mail_file_storage_type_flash; att.descr.transfer_encoding = Content_Transfer_Encoding::enc_base64; And add this attachment to the message: message.addAttachment(att); Finally, you just need to send the message as you did with the previous example: if (!MailClient.sendMail(&smtp, &message, true)) Serial.println("Error sending Email, " + smtp.errorReason());

Demonstration

After uploading the code, open the Serial Monitor at a baud rate of 115200 and press the on-board EN/RESET button. If everything goes smoothly, you should get a similar message on the Serial Monitor. Check the recipient's email address. You should have a new email with two attachments.

Wrapping Up

In this tutorial you've learned how to send emails with the ESP32 using an SMTP Server. For this method to work, the ESP32 should have access to the internet. If you don't want to use an SMTP Server, you can also write a PHP script to send email notifications with the ESP32 or ESP8266 board. You've learned how to send a simple email with text and with attachments. When using attachments, these should be saved on the ESP32 filesystem (LittleFS) or on a microSD card (not covered in this tutorial). The examples presented show how to send a single email when the ESP32 boots. The idea is to modify the code and include it in your own projects. For example, it can be useful to send a .txt file with the sensor readings, send a photo captured with the ESP32-CAM, use deep sleep to wake up your board every hour and send an email with data, etc.

Upload Files to LittleFS using Arduino IDE

In this guide, you'll learn how to upload files to the ESP32 Filesystem (LittleFS) by using a plugin for Arduino IDE (1.8.X). LittleFS is a lightweight filesystem created for microcontrollers that lets you access the flash memory like you would do in a standard file system on your computer, but simpler and more limited. The plugin we'll install lets you use three different filesystems: LittleFS, SPIFFS, or FatFS. At the moment, this method is not compatible with Arduino 2.0. So, you should be using Arduino IDE version 1.8.X. If you want to use LittleFS with the ESP8266, read: Install ESP8266 NodeMCU LittleFS Filesystem Uploader in Arduino IDE. Table of Contents Introducing LittleFS Installing the Arduino ESP32 filesystem uploader Windows Instructions Mac OS X Instructions Uploading Files to ESP32 LittleFS using the Filesystem Uploader Testing the ESP32 Filesystem Uploader

Introducing LittleFS

LittleFS is a lightweight filesystem created for microcontrollers that lets you access the flash memory like you would do in a standard file system on your computer, but it's simpler and more limited. You can read, write, close, and delete files. Using a filesystem with the ESP32 boards is especially useful to: Create configuration files with settings; Save data permanently; Create files to save small amounts of data instead of using a microSD card; Save HTML, CSS, and JavaScript files to build a web server; Save images, figures, and icons; And much more.

Installing the Arduino ESP32 filesystem uploader

Currently, there is a plugin for the Arduino IDE (version 1.8.X) that allows you to pack and upload files to the SPIFFS, LittleFS, or FatFS filesystem image in the ESP32 filesystem. Note: in most of our projects we use SPIFFS for the ESP32 filesystem. It's still compatible with the ESP32, and you can use SPIFFS without any issues. However, currently, many libraries are moving to LittleFS. The plugin we'll install is both compatible with SPIFFS and LittleFS. So, it's an advantage over the older plugin and you can still use SPIFFS. There are a few advantages of using LittleFS over SPIFFS: LittleFS is optimized for low resource usage and it employs a wear-leveling algorithm that evenly distributes writes across the flash memory, prolonging its lifespan. LittleFS provides faster mount times and file access by utilizing a directory indexing structure. LittleFS minimizes the risk of data corruption during power loss or system failures. LittleFS is under active development.

Windows Instructions

Follow the next steps to install the filesystem uploader if you're using Windows: 1) Go to the releases page and click the latest esp32fs.zip file to download. 2) Unzip the downloaded file. You should have a folder called esp32fs with a file called esp32fs.jar inside. 3) Find your Sketchbook location. In your Arduino IDE, go to File > Preferences and check your Sketchbook location. In my case, it's in the following path: C:\Users\sarin\Documents\Arduino. 4) Go to the sketchbook location, and create a tools folder if you don't have it already (make sure that the Arduino IDE application is closed). 5) Inside the tools folder, create another folder called ESP32FS if you haven't already. 6) Inside the ESP32FS folder, create a folder called tool. 7) Copy the esp32fs.jar file to the tool folder (if you already have an esp32fs.jar file from a previous plugin, delete it and replace it with the new one). So, the directory structure will look like this: <home_dir>/Arduino/tools/ESP32FS/tool/esp32fs.jar 8) Now, you can open Arduino IDE. To check if the plugin was successfully installed, open your Arduino IDE and select your ESP32 board. In the Tools menu, check that you have the option ESP32 Sketch Data Upload. Click on that option. A window will pop up for you to choose the filesystem you want to use. As you can see, you have the option to choose from LittleFS, SPIFFS, or FatFS and you can even have the option to erase flash if needed. Congratulations! You've successfully installed the Filesystem uploader plugin for the ESP32 on the Arduino IDE.

Mac OS X Instructions

Follow the next instructions if you're using MacOS X. 1) Go to the releases page and click the latest esp32fs.zip file to download. 2) Unpack the files. You should have a folder called esp32fs with a file called esp32fs.jar inside. 3) Create a folder called tools in /Documents/Arduino/ if you haven't already. 4) Inside the tools folder create another one called ESP32FS. 5) Inside the ESP32FS folder, create a folder called tool. So, the directory structure will look like this: <home_dir>/Arduino/tools/ESP32FS/tool/ 6) Copy the unpacked esp32fs.jar file to the tool directory (if you already have an esp32fs.jar file from a previous plugin, delete it and replace it with the new one). You should have a similar folder structure. 7) Now, you can open Arduino IDE. To check if the plugin was successfully installed, open your Arduino IDE and select your ESP32 board. In the Tools menu, check that you have the option ESP32 Sketch Data Upload. Click on that option. A window will pop up for you to choose the filesystem you want to use. As you can see, you have the option to choose from LittleFS, SPIFFS, or FatFS and you can even have the option to erase flash if needed. Congratulations! You've successfully installed the Filesystem uploader plugin for the ESP32 on the Arduino IDE.

Uploading Files using the Filesystem Uploader

To upload files to the ESP32 LittleFS filesystem follow the next instructions. 1) Create an Arduino sketch and save it. For demonstration purposes, you can save an empty sketch. 2) Then, open the sketch folder. You can go to Sketch > Show Sketch Folder. The folder where your sketch is saved should open. Arduino IDE Show Sketch folder to create data folder 3) Inside that folder, create a new folder called data. ESP32 Arduino Sketch Example File Filesystem fs LittleFS 4) Inside the data folder is where you should put the files you want to save into the ESP32 filesystem. As an example, create a .txt file with some text called test_example. ESP32 Notepad Test Example File Filesystem fs LittleFS 5) Then, to upload the files, in the Arduino IDE, you just need to go to Tools > ESP32 Sketch Data Upload. ESP32 Sketch Data Upload Arduino IDE FS Filesystem 6. Select the LittleFS option and click OK. Make sure the Serial Monitor is closed before uploading the files, otherwise, you'll get an error related to the Serial communication, and the files won't upload. The uploader will overwrite anything you had already saved in the filesystem. Note: in some ESP32 development boards you need to press the on-board BOOT button for around two seconds to upload the files. The files were successfully uploaded to the ESP32 filesystem when you see the message LittleFS Image Uploaded.

Testing the Filesystem Uploader Plugin

Now, let's just check if the file was actually saved into the ESP32 filesystem. Simply upload the following code to your ESP32 board. /********* Rui Santos Complete project details at https://RandomNerdTutorials.com/esp32-littlefs-arduino-ide/ *********/ #include "LittleFS.h" void setup() { Serial.begin(115200); if(!LittleFS.begin()){ Serial.println("An Error has occurred while mounting SPIFFS"); return; } File file = LittleFS.open("/test_example.txt"); if(!file){ Serial.println("Failed to open file for reading"); return; } Serial.println("File Content:"); while(file.available()){ Serial.write(file.read()); } file.close(); } void loop() { } View raw code After uploading, open the Serial Monitor at a baud rate of 115200. Press the ESP32 ENABLE/RST button. It should print the content of your .txt file on the Serial Monitor. ESP32 LittleFS Filesystem Example Arduino IDE Serial Monitor You've successfully uploaded files to the ESP32 filesystem using the plugin.

Wrapping Up

In this tutorial, you installed a plugin for the Arduino IDE that allows you to upload files to the ESP32 filesystem. This plugin supports three different filesystems: SPIFFS, LittleFS, and FatFS. While many libraries and projects are moving to LittleFS, SPIFSS is still used and your previous projects that use SPIFFS should still be working. Because this new plugin supports both SPIFFS and LittleFS, you should consider installing this one instead of the SPIFFS plugin so that you have more flexibility with the choice of filesystem.

26 Free Guides for Sensors and Modules

There is a wide variety of sensors, modules, and peripherals compatible with the ESP32 boards. We have tutorials for the most popular components. This article is a compilation of 26 free guides for ESP32 sensors and modules. Most guides cover programming the ESP32 using the Arduino core, but we also have tutorials for MicroPython. We have a similar article for the ESP8266: 20 Free Guides for Sensors and Modules [ESP8266]. Here's a quick list of the sensors/modules: Environmental Sensors:
    DS18B20 Temperature Sensor Type-K Thermocouple Temperature Sensor DHT11/DHT22 Temperature and Humidity Sensor BME280 Temperature, Humidity, and Pressure Sensor BME680 Environmental Sensor (Gas, Pressure, Humidity, Temperature) BMP388 Altimeter Sensor (Pressure, Altitude, Temperature) BMP180 Barometric Sensor (Pressure, Altitude, Temperature) BH1750 Light Sensor TDS Sensor (Total Dissolved Solids)
Motion-Related Sensors:
    PIR Motion Sensor Door Sensor (reed switch) HC-SR04 Ultrasonic Sensor MPU6050 Accelerometer and Gyroscope RCWL-0516 Microwave Radar Proximity Sensor
Other Sensors/Modules/Peripherals:
    microSD Card Module Potentiometer Relay Module Load Cell with HX711 Amplifier
Displays:
    OLED Display (SSD1306) I2C LCD (Liquid Crystal Display) RGB LED Strip
Communication:
    LoRa Transceiver I2C Multiplexer (TCA9548A)
Motors:
    Servo Motor DC Motor Stepper Motor

Environmental Sensors

1. DS18B20 Temperature Sensor

The DS18B20 temperature sensor is a one-wire digital temperature sensor. This means that it just requires one data line (and GND) to communicate with your ESP32. Each DS18B20 temperature sensor has a unique 64-bit serial code. This allows you to wire multiple sensors to the same data wire. So, you can get temperature from multiple sensors using just one GPIO. The DS18B20 temperature sensor is also available in waterproof version. To get started, you can follow the next tutorials: Arduino core: ESP32 DS18B20 Temperature Sensor with Arduino IDE (Single, Multiple, Web Server) ESP32 with Multiple DS18B20 Temperature Sensors MicroPython: MicroPython: DS18B20 Temperature Sensor with ESP32 and ESP8266 Get a DS18B20 Temperature Sensor. Get a DS18B20 Temperature Sensor (waterproof version).

2. Type-K Thermocouple Temperature Sensor

A K-type thermocouple is a type of temperature sensor with a wide measurement range like 200 to 1260oC (326 to 2300oF). To get the temperature from the thermocouple we need a thermocouple amplifier. We use the MAX6675 amplifier that is sold together with the thermocouple, but you can use any other amplifier, like the MAX31855. To get started, follow the next tutorial: Arduino core: ESP32: K-Type Thermocouple with MAX6675 Amplifier Get the Type-K Thermocouple temperature sensor.

3. DHT11/DHT22 Temperature and Humidity Sensor

The DHT11 and DHT22 sensors are used to measure temperature and relative humidity. These sensors contain a chip that does analog to digital conversion and spits out a digital signal with the temperature and humidity. This makes them very easy to use with any microcontroller. To get started, follow the next tutorials: Arduino Core: ESP32 with DHT11/DHT22 Temperature and Humidity Sensor using Arduino IDE MicroPython: MicroPython: ESP32/ESP8266 with DHT11/DHT22 Temperature and Humidity Sensor Get a DHT22 temperature and humidity sensor. Get a DHT11 temperature and humidity sensor.

4. BME280 Temperature, Humidity, and Pressure Sensor

The BME280 sensor module reads barometric pressure, temperature, and humidity. Because pressure changes with altitude, you can also estimate altitude. There are several versions of this sensor module: some can communicate using only I2C communication protocol, and others have the additional option to use the SPI communication protocol. We usually use the I2C protocol with this sensor. This sensor is very versatile and we use it in many of our tutorials. To get started, follow the next tutorials: Arduino core: ESP32 with BME280 Sensor using Arduino IDE (Pressure, Temperature, Humidity) MicroPython: MicroPython: BME280 with ESP32 and ESP8266 (Pressure, Temperature, Humidity) Get a BME280 temperature, humidity, and pressure sensor.

5. BME680 Environmental Sensor (Gas, Pressure, Humidity, Temperature)

The BME680 is an environmental sensor that combines gas, pressure, humidity, and temperature sensors. The gas sensor can detect a broad range of gases like volatile organic compounds (VOC). For this reason, the BME680 can be used in indoor air quality control. To get started, follow the next tutorials: Arduino core: ESP32: BME680 Environmental Sensor using Arduino IDE (Gas, Pressure, Humidity, Temperature) MicroPython: MicroPython: BME680 with ESP32 and ESP8266 (Temperature, Humidity, Pressure, Gas) Get a BME680 environmental sensor.

6. BMP388 Altimeter Sensor (Pressure, Altitude, Temperature)

The BMP388 is a precise, low-power, low-noise absolute barometric pressure sensor that measures absolute pressure and temperature. Because pressure changes with altitude, we can also estimate altitude with great accuracy. For this reason, this sensor is handy for drone navigation and other applications like vertical velocity calculation; internet of things; weather forecast, and weather stations; health care applications; fitness applications; and much more. To get started, follow the next tutorial: Arduino core: ESP32 with BMP388 Barometric/Altimeter Sensor (Arduino IDE) Get a BMP388 altimeter sensor.

7. BMP180 Barometric Sensor (Pressure, Altitude, Temperature)

The BMP180 is a digital pressure sensor and it measures the absolute pressure of the air around it. It features a measurement range from 300 to 1100hPa with an accuracy down to 0.02 hPa. Because temperature affects the pressure, the sensor comes with a temperature sensor to give temperature compensated pressure readings. Additionally, because the pressure changes with altitude, you can also estimate the altitude based on the current pressure measurement. The sensor communicates with a microcontroller using I2C communication protocol. To get started, follow the next tutorial: Arduino core: ESP32 with BMP180 Barometric Sensor Guide Get a BMP180 barometric sensor.

8. BH1750 Light Sensor

The BH1750 is a 16-bit ambient light sensor that communicates via I2C protocol. It outputs luminosity measurements in lux (SI-derived unit of illuminance). It can measure a minimum of 1 lux and a maximum of 65535 lux. It can be used in a wide variety of projects. For example: to detect if it is day or night; to adjust or turn on/off LED's brightness accordingly to ambient light; to adjust LCDs and screen's brightness; to detect if an LED is lit; etc. To get started, follow the next tutorial: Arduino core: ESP32 with BH1750 Ambient Light Sensor Get a BH1750 ambient light sensor.

9. TDS Sensor (Total Dissolved Solids)

A TDS meter indicates the total dissolved solids like salts, minerals, and metals, in a solution. This parameter can be used to give you an idea of water quality and compare water from different sources. One of the main applications of a TDS meter is aquarium water quality monitoring. To get started, follow the next tutorial: Arduino core: ESP32 with TDS Sensor (Water Quality Sensor) Get a TDS (total dissolved solids) sensor.

Motion Related Sensors

10. PIR Motion Sensor

The PIR motion sensor is ideal to detect movement. PIR stands for Passive Infrared and it measures infrared light from objects in its field of view. So, it can detect motion based on changes in infrared light in the environment. It is ideal to detect if a human or animal has moved in or out of the sensor range. Get started with the following tutorials: Arduino core: ESP32 with PIR Motion Sensor using Interrupts and Timers MicroPython: MicroPython: Interrupts with ESP32 and ESP8266 (PIR motion sensor) Get a PIR Motion Sensor (HC-SR501).

11. Door Sensor (reed switch)

A magnetic contact switch is a reed switch encased in a plastic shell so that you can easily apply it on a door, a window, or a drawer to detect if it is open or closed. We have several tutorials with the ESP32 that use a reed switch and send notifications when the door is opened or closed: ESP32 Door Status Monitor with Telegram Notifications ESP32 Door Status Monitor with Email Notifications (IFTTT) Get a magnetic reed switch.

12. HC-SR04 Ultrasonic Sensor

The HC-SR04 ultrasonic sensor uses sonar to determine the distance to an object. This sensor reads from 2cm to 400cm (0.8inch to 157inch) with an accuracy of 0.3cm (0.1inches), which is good for most hobbyist projects. In addition, this particular module comes with ultrasonic transmitter and receiver modules. Get started with one of the following tutorials: Arduino core: ESP32 with HC-SR04 Ultrasonic Sensor with Arduino IDE MicroPython: MicroPython: HC-SR04 Ultrasonic Sensor with ESP32 and ESP8266 (Measure distance) Get an HC-SR04 ultrasonic sensor.

13. MPU6050 Accelerometer and Gyroscope

The MPU-6050 IMU (Inertial Measurement Unit) is a 3-axis accelerometer and 3-axis gyroscope sensor. The accelerometer measures the gravitational acceleration and the gyroscope measures the rotational velocity. Additionally, this module also measures temperature. This sensor is ideal to determine the orientation of a moving object. To get started, follow the next tutorial: Arduino core: ESP32 with MPU-6050 Accelerometer, Gyroscope, and Temperature Sensor (Arduino) Get an MPU6050 accelerometer and gyroscope.

14. RCWL-0516 Microwave Radar Proximity Sensor

The RCWL-0516 is a small, inexpensive sensor that uses microwave radar to detect the presence of moving objects. The RCWL-0516 sensor has a single output pin that goes HIGH when it detects movement. It outputs LOW when no motion is detected. This sensor is many times used as an alternative to the PIR motion sensor. Get started with the following tutorial: ESP32 with RCWL-0516 Microwave Radar Proximity Sensor (Arduino IDE) Get an RCWL-0516 Microwave Radar Proximity Sensor.

Other Sensors/Modules/Peripherals

15. microSD Card Module

The microSD card module allows you to interface the ESP32 with a microSD card. You can use the microSD card with the ESP32 to create, write, read, and delete files. It can be very useful for datalogging, to save configuration files or save files to serve to clients via a web server. To get started, follow the next tutorial: Arduino core: ESP32: Guide for MicroSD Card Module using Arduino IDE Get a microSD card module.

16. Potentiometer

A potentiometer, also referred to as a pot, is a manually adjustable resistor that can be used in numerous applications: adjust the speed of a DC motor, adjust the position of a stepper or servo motor, adjust threshold values, adjust light intensity, and much more. To get a value from a potentiometer, you need to know how to read analog signals with the ESP32. Get started with the following tutorials: Arduino core: ESP32 ADC Read Analog Values with Arduino IDE (potentiometer) MicroPython: ESP32/ESP8266 Analog Readings with MicroPython To learn how a potentiometer works, we recommend taking a quick look at the following guide: Electronics Basics How a Potentiometer Works Get a potentiometers assortment kit.

17. Relay Module

A relay is an electrically operated switch and like any other switch, it that can be turned on or off, letting the current go through or not. It can be controlled with low voltages, like the 3.3V provided by the ESP32 GPIOs, and allows us to control high voltages like 12V, 24V, or mains voltage (230V in Europe and 120V in the US). Using a relay with the ESP32 is a great way to control AC household appliances remotely. Get started with the following tutorials: Arduino core: ESP32 Relay Module Control AC Appliances MicroPython: MicroPython: ESP32/ESP8266 Relay Module Web Server (AC Appliances) Get a relay module: 5V 2-channel relay module (with optocoupler) 5V 1-channel relay module (with optocoupler) 5V 8-channel relay module (with optocoupler) 5V 16-channel relay module (with optocoupler) 3.3V 1-channel relay module (with optocoupler)

18. Load Cell with HX711 Amplifier

The load cell you see in the picture above is a strain gauge load cell. A strain gauge is an electrical sensor that measures force or strain on an object. The resistance of the strain gauge varies when an external force is applied to an object, which results in a deformation of the object's shape (in this case, the metal bar). The change of the resistance is proportional to the load applied, which allows us to calculate the weight of objects. Get started with the following tutorial: Arduino core: ESP32 with Load Cell and HX711 Amplifier (Digital Scale) Get a load cell with the HX711 amplifier.

Displays

19. OLED Display (SSD1306)

The organic light-emitting diode (OLED) display is a monocolor display that doesn't require backlight, which results in a very nice contrast in dark environments. Additionally, its pixels consume energy only when they are on, so the OLED display consumes less power when compared with other displays. It's available with different drivers, but we recommend getting the one with the SSD1306 driver, which is the most supported. There is also a wide variety of OLED sizes. We usually use the 0.96-inch display with 12864 pixels. There are also ESP32 boards with a built-in OLED display. This is very useful because you don't need any extra circuitry if you want to add a physical visual interface to your project: ESP32 Built-in OLED Board (Wemos Lolin32): Pinout, Libraries and OLED Control. Arduino core: ESP32 OLED Display with Arduino IDE ESP32/ESP8266: DHT Temperature and Humidity Readings in OLED Display MicroPython: MicroPython: OLED Display with ESP32 and ESP8266 MicroPython: SSD1306 OLED Display Scroll Functions and Draw Shapes (ESP32/ESP8266) Get an 0.96inch SSD1306 OLED display.

20. I2C LCD (Liquid Crystal Display)

The simplest and cheapest display screen around is the liquid crystal display (LCD). LCDs are found in everyday electronics devices like vending machines, calculators, parking meters, and printers, and are ideal for displaying text or small icons. LCDs are measured according to the number of rows and columns of characters that fit on the screen. You'll find sizes ranging from 81 to 404. A 162 LCD can display 2 rows of 16 characters each and this is the one we use most in our projects. We recommend getting one that supports I2C because it makes wiring and coding even easier. Get started with the following tutorial: Arduino core: How to Use I2C LCD with ESP32 on Arduino IDE (ESP8266 compatible) Get an I2C LCD display.

21. RGB LED Strip

LED strips are just amazing, and there are a wide variety of LED strips to choose from. They can be analog, or digital, and vary in the density and number of LEDs, power supply, etc. To learn more about the main differences between LED strips, I recommend taking a look at the following article: What's the Best LED Strip For Your Project? Analog LED strips have their LEDs wired in parallel. The whole strip works as a giant RGB LED. So, you can light up your whole strip in many different colors, but you can't control LEDs individually. This means your strip can only be one color at a time. This type of LED strips are cheaper than the digital ones and easier to use. You can follow the next tutorial that shows how to use those LED strips: Arduino core: ESP32/ESP8266 RGB LED Strip with Color Picker Web Server When it comes to digital LED strips, you can control each LED individually these are also called addressable LED strips. You can chose each LED color, its brightness and when they should be on and off. This allows you to do all sorts of crazy and awesome effects. Our favorite addressable RGB LED strip is the WS2812B. We have a MicroPython guide showing how to control an addressable RGB LED strip and produce amazing effects. MicroPython: MicroPython: WS2812B Addressable RGB LEDs with ESP32 and ESP8266 Get an Analog RGB LED Strip. Get a WS2812B addressable RGB LED Strip.

Communication

22. LoRa Transceiver

LoRa is a wireless data communication technology that uses a radio modulation technique that can be generated by Semtech LoRa transceiver chips. This modulation technique allows long-range communication of small amounts of data (which means a low bandwidth), and high immunity to interference while minimizing power consumption. So, it allows long-distance communication with low power requirements. To use LoRa in your projects, you can use a transceiver like the RFM95 or use an ESP32 with a built-in LoRa transceiver module. Get started with the following tutorials: Arduino core: ESP32 with LoRa using Arduino IDE Getting Started TTGO LoRa32 SX1276 OLED Board: Getting Started with Arduino IDE Get an RFM95 LoRa transceiver module. Get an ESP32 with a built-in LoRa chip.

23. TCA9548A I2C Multiplexer

The I2C communication protocol allows you to communicate with multiple I2C devices on the same I2C bus as long as each device has a unique I2C address. However, it will not work if you want to connect multiple I2C devices with the same address. The TCA9548A I2C multiplexer allows you to communicate with up to 8 I2C devices with the same I2C bus. The multiplexer communicates with a microcontroller using the I2C communication protocol. Then, you can select which I2C bus on the multiplexer you want to address. Get started with the following tutorial: Arduino core: Guide for TCA9548A I2C Multiplexer: ESP32, ESP8266, Arduino Get a TCA9584A I2C multiplexer.

Motors

24. Servo Motor

Learn how to control a servo motor with the ESP32 remotely using a web server: ESP32 Servo Motor Web Server with Arduino IDE Get a micro servo motor.

25. DC Motor and L298N Motor Driver

Learn how to control a DC motor (speed and direction) with the ESP32 using the L298N motor driver. To get started, follow the next tutorial: Arduino core: ESP32 with DC Motor and L298N Motor Driver Control Speed and Direction Get a mini DC motor. Get a L298N motor driver.

26. Stepper Motor

A stepper motor is a brushless DC electric motor that divides a full rotation into a number of steps. It moves one step at a time, and each step is the same size. This allows us to rotate the motor at a precise angle to a precise position. The stepper motor can rotate clockwise or counterclockwise. To get started, follow the next tutorial: Arduino core: ESP32 with Stepper Motor (28BYJ-48 and ULN2003 Motor Driver) Get a stepper motor (2BYJ-48).

Wrapping Up

This was our compilation of tutorials for the most popular sensors, modules, and peripherals compatible with the ESP32. If you have a sensor/module that you would like to be covered on our website, just write a comment below.

Send Pushover Notifications (Arduino IDE)

In this guide, you'll learn how to send ESP32 notifications to Pushover. Pushover is a notification service that integrates with many applications. You can send push notifications to all of your devices and multiple users. You can manage your notifications with levels of priority, set silent hours, and set different sounds depending on the notification. Table of Contents Introducing Pushover App Installing Pushover App Getting Pushover API Key and User Key Pushover Notifications with the ESP32 Example Sketch (Arduino IDE) Sending Pushover Notifications with the ESP32 Demonstration New to the ESP32? Start here: Getting Started with the ESP32 Development Board.

Introducing Pushover

Pushover is a mobile and desktop app compatible with Android and iOS and Windows, MacOS, and Linux. It allows you to receive notifications from different sources and services and integrates with many applications. You can receive notifications on all your devices simultaneously or send them to groups with multiple users. Additionally, you can customize things such as priority levels, set silent hours, and even different sounds depending on the type of notification. After publishing this previous tutorial, we found that many of our readers were already using Pushover for other types of notifications. So, we thought it would be useful to know how to integrate Pushover with the ESP32 so that you have all your notifications in one place.

Pushover Pricing

You have a free 30-day trial period from the time you register so that you can experiment with the app. After that, if you want to continue using the app, it's just a one-time $5 USD purchase. Each user can send up to 10,000 messages per month for free.

Installing Pushover App

You can install the Pushover app on your computer, tablet, and smartphone. It is compatible with Windows, MacOS, and Linux, and with Android and iOS. Download the app into your smartphone and create an account to get started. After creating your account, you'll have a free 30-day trial period. After that, you'll need to go to your email to verify your account.

Getting Pushover API Key and User Key

To send notifications to Pushover with the ESP32, we need to get an API key and the user (receiver) key. For this step, we recommend logging in to your pushover account on your computer browser. Login here: https://pushover.net/login. You'll get access to your Pushover dashboard. At the top right corner, there's the User Key. Save it because you'll need it later. You can also see all your devices and add more devices if you want. You can try to Push a Notification at the top left corner to check if notifications are working with your device. Scroll down the page to create an Application/API Token. Give a name and description(optional) to the API Token. You can also add an icon to that. We added an icon of an ESP32. So, when we receive a notification, it will be accompanied by the ESP32 icon. Finally, create the application. Now, the application will show up on your dashboard under the Your Applications section. Click on the application name to get its API token. On that menu, you can also check how many notifications you have sent with that API token. Save the API token because you'll need it later. Now that you have the user key and API token, you can start sending ESP32 notifications with Pushover.

Pushover Notifications with the ESP32 Example Sketch

Sending Pushover notifications with the ESP32 is very straightforward thanks to its API. You can read Pushover's API documentation here. You simply need to POST an HTTPS request with the ESP32 with the right parameters to the API endpoint: https://api.pushover.net/1/messages.json. You must pass the following parameters: token: your application's API token user: your user key or user group key message: the notification content You can also pass other optional parameters: attachment: an image attachment to send with the message. device: the name of the device you want to receive the notification. html: set to 1 to enable HTML parsing. priority: set the notification priority level: a value of -2, -1, 0 (default), 1, or 2. sound: the name of a supported sound to override your default sound choicevalues can be pushover, bike, bugle, cashregister, classical, cosmic, etc (check all available sound options here). You can even upload your own sounds to your Pushover dashboard. timestamp: a Unix timestamp of a time to display instead of when our API received the request. title: your message's title, otherwise your app's name is used. url: a supplementary URL to show with your message (documentation). url_title: a title for the URL specified as the url parameter, otherwise just the URL is shown. For more information about all the parameters, please check Pushover's API documentation. The code below shows how to send Pushover notifications with the ESP32 using an HTTPS POST request. Before uploading the code to your board you need to insert your SSID and password, the user key, and Pushover App API token. /* Rui Santos Complete project details at https://RandomNerdTutorials.com/esp32-pushover-notifications-arduino/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ #include <WiFi.h> #include <HTTPClient.h> #include <WiFiClientSecure.h> #include <ArduinoJson.h> const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; const char* apiToken = "API_TOKEN"; const char* userToken = "USER_TOKEN"; //Pushover API endpoint const char* pushoverApiEndpoint = "https://api.pushover.net/1/messages.json"; //Pushover root certificate (valid from 11/10/2006 to 11/10/2031) const char *PUSHOVER_ROOT_CA = "-----BEGIN CERTIFICATE-----\n" "MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\n" "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\n" "QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\n" "MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n" "b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n" "9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\n" "CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\n" "nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n" "43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\n" "T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\n" "gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\n" "BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\n" "TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\n" "DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\n" "hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n" "06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\n" "PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\n" "YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\n" "CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n" "-----END CERTIFICATE-----\n"; void setup() { Serial.begin(115200); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connecting to WiFi..."); } Serial.println("Connected to WiFi"); //Make HTTPS POST request to send notification if (WiFi.status() == WL_CONNECTED) { // Create a JSON object with notification details // Check the API parameters: https://pushover.net/api StaticJsonDocument<512> notification; notification["token"] = apiToken; //required notification["user"] = userToken; //required notification["message"] = "Hello from ESP32"; //required notification["title"] = "ESP32 Notification"; //optional notification["url"] = ""; //optional notification["url_title"] = ""; //optional notification["html"] = ""; //optional notification["priority"] = ""; //optional notification["sound"] = "cosmic"; //optional notification["timestamp"] = ""; //optional // Serialize the JSON object to a string String jsonStringNotification; serializeJson(notification, jsonStringNotification); // Create a WiFiClientSecure object WiFiClientSecure client; // Set the certificate client.setCACert(PUSHOVER_ROOT_CA); // Create an HTTPClient object HTTPClient https; // Specify the target URL https.begin(client, pushoverApiEndpoint); // Add headers https.addHeader("Content-Type", "application/json"); // Send the POST request with the JSON data int httpResponseCode = https.POST(jsonStringNotification); // Check the response if (httpResponseCode > 0) { Serial.printf("HTTP response code: %d\n", httpResponseCode); String response = https.getString(); Serial.println("Response:"); Serial.println(response); } else { Serial.printf("HTTP response code: %d\n", httpResponseCode); } // Close the connection https.end(); } } void loop() { } View raw code

How the Code Works

Continue reading the learn how the code works, or skip to the demonstration section.

Include Libraries

You start by including the required libraries. The WiFi library to connect the ESP32 to your network, so that it can connect to the internet. The HTTPClient and WiFiClientSecure libraries will be used to make secure HTTP POST requests, and we'll use the ArduinoJSON library to create a JSON string to send all the required parameters on the body of the HTTP POST request. #include <WiFi.h> #include <HTTPClient.h> #include <WiFiClientSecure.h> #include <ArduinoJson.h>

Network Credentials

Insert your network credentials in the following variables. const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD";

API Token and User Token

Insert the API token and user token on the following variables: const char* apiToken = "API_TOKEN"; const char* userToken = "USER_TOKEN";

API Endpoint

Then, we set the API endpoint URL where we'll make the requests. According to the documentation, it is as follows: const char* pushoverApiEndpoint = "https://api.pushover.net/1/messages.json";

SSL Certificate

To make secure HTTPS requests, we need the pushover's website TLS certificate. We're using the root certificate. It is valid until 2031. To learn how to get a website's TLS certificate, you can read this: Getting a Server's Certificate using Google Chrome. const char *PUSHOVER_ROOT_CA = "-----BEGIN CERTIFICATE-----\n" "MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\n" "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\n" "QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\n" "MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n" "b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n" "9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\n" "CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\n" "nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n" "43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\n" "T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\n" "gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\n" "BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\n" "TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\n" "DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\n" "hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n" "06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\n" "PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\n" "YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\n" "CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n" "-----END CERTIFICATE-----\n";

Connect to Wi-Fi

In the setup(), start by connecting the ESP32 to your network: Serial.begin(115200); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connecting to WiFi..."); } Serial.println("Connected to WiFi"); Learn more about ESP32 Wi-Fi functions: ESP32 Useful Wi-Fi Library Functions (Arduino IDE).

Setting Notification Parameters

After making sure we are connected to Wi-Fi, we create a JSON object called notification with the required parameters and some of the optional parameters. The required parameters are the API token, user token and the message. StaticJsonDocument<512> notification; notification["token"] = apiToken; //required notification["user"] = userToken; //required notification["message"] = "Hello from ESP32"; //required notification["title"] = "ESP32 Notification"; //optional notification["url"] = ""; //optional notification["url_title"] = ""; //optional notification["html"] = ""; //optional notification["priority"] = ""; //optional notification["sound"] = "cosmic"; //optional notification["timestamp"] = ""; //optional As you can see, the message is set on the following line: notification["message"] = "Hello from ESP32"; //required You can change the message to whatever it's useful for your application. We also set the message title on the following line: notification["title"] = "ESP32 Notification"; //optional After getting all the parameters in a JSON object, we convert it to a String so that we can send it in the body of the HTTP POST request. String jsonStringNotification; serializeJson(notification, jsonStringNotification);

HTTPS POST Request

Now, we can finally make the HTTPS POST request. Learn more about secure HTTPS requests with the ESP32: ESP32 HTTPS Requests (Arduino IDE). Learn more about HTTP POST requests with the ESP32: ESP32 HTTP POST Requests. Create a WiFiClientSecure object called client. WiFiClientSecure client; Set a secure client with the certificate using the setCACert() method: client.setCACert(PUSHOVER_ROOT_CA); Then, create an HTTPClient instance called https. HTTPClient https; Initialize the https client on the host specified using the begin() method. In this case, we're making a request on the API endpoint. https.begin(client, pushoverApiEndpoint); Then, add the HTTP POST headerswe need to specify that we're going to send the data as a JSON string in the request body. // Add headers https.addHeader("Content-Type", "application/json"); Finally, we can send the POST request with the JSON data. // Send the POST request with the JSON data int httpResponseCode = https.POST(jsonStringNotification); After making the request, we can check the server response. This is useful to know if the request succeeded or if there were any problems during the request or with the request body. To get the server response, we simply need to use the getString() method on the https object. if (httpResponseCode > 0) { Serial.printf("HTTP response code: %d\n", httpResponseCode); String response = https.getString(); Serial.println("Response:"); Serial.println(response); } else { Serial.printf("HTTP response code: %d\n", httpResponseCode); } Finally, close the HTTPS connection using the end() method: https.end(); This example sends a notification in the setup() when the ESP32 first runs. So, the loop() is empty. The idea is that you use this example in your own application. For example, to send a notification when motion is detected, when a sensor reading is above or below a certain threshold, to send sensor readings or GPIO states regularly, and many other possibilities in the home automation and IoT fields.

Demonstration

After inserting the SSID, password, user key, and API key, you can upload the code to your board. After uploading, open the Serial Monitor at a baud rate of 115200. Press the ESP32 RST button so that it starts running the code. You should get a success message (response code: 200) on your Serial Monitor. And you should receive the notification on your smartphone. You can click on the message to open it.

Pushover Settings

On your device, you can adjust the settings for your notifications. Click on the () icon at the top right corner. You'll get a page as shown below. You can set quiet hours and other settings like volume and sound for critical alerts, add custom sounds, and more.

Wrapping Up

Pushover is a notification service that you can use to receive notifications from different apps and services in the same place. You can manage your notifications in terms of priority, set silence hours, and even set different sounds depending on the notification received. In this tutorial, you learned how to send ESP32 notifications to Pushover using Pushover's API. Using the Pushover app is not free, but you have a 30-day free trial. If you decide that it is useful for your projects, you can have access to the app with a one-time payment fee of just 5$.

How to Log Data (9 Different Ways)

In this article, we'll share nine different methods to log and save data with the ESP32. Would you like to monitor a specific sensor with the ESP32 and keep track of the data over time? Would you like to save all your data records but you don't know how to do it or which method to use? Here we'll show you different ways to save data permanently with the ESP32. We'll show you methods that rely on third-party databases, services, hardware solutions, and others. Table of Contents: Throughout this article, we'll cover the following methods to save data using the ESP32:
    MicroSD Card ESP32 Filesystem (SPIFFS or LittleFS) Firebase Realtime Database and Firebase Storage InfluxDB Time Series Database ">Deta Base: Free and Unlimited Database Thingspeak Channels Google sheets MySQL on a Cloud Server MySQL on a Local Server

1) ESP32 Datalogging using a MicroSD Card

Using a microSD card with the ESP32 is a great way to save data permanently. You can save big amounts of data in txt or in other formatsas much as the microSD card size allows you to. You can also save pictures if you're using an ESP32-CAM. To interface a microSD card with the ESP32, you can use a microSD card module that communicates with the board via SPI communication protocol. These modules are usually pretty cheap. You can check some microSD card modules here. Some ESP32 development boards already come with a built-in microSD card slot, so you won't need any extra circuitry or hardware. We have a complete guide showing how to interface a microSD card with the ESP32 and how to handle filesread, write, create, delete, append, and much more. ESP32: Guide for MicroSD Card Module using Arduino IDE If you want a specific datalogging example with sensors, we also have the following projects (you can easily modify the projects to use other sensors): Altimeter Datalogger: ESP32 with BMP388, MicroSD Card Storage and OLED Display ESP32 Data Logging Temperature to MicroSD Card Advantages of using a microSD card with the ESP32 for datalogging: Low-cost solution: microSD card modules are very cheap. Huge storage capacity: microSD cards are widely available and can save huge amounts of data. Easy to use: it is easy to connect and easy to program with the ESP32. Doesn't require a connection to the internet: so it's an interesting solution in remote places. It can save data in different file formats. However, you need to take into account some of the following disadvantages: Extra hardware: unless your board already comes with a built-in microSD card slot, you'll need to get a microSD card module. Data corruption: after long periods of using the microSD card, it might be corrupted and you'll lose all your data; Data is not accessible in real-time: you need to remove the microSD card from the board and insert it into your computer to access the data (unless you build a webserver that serves the microSD card files online).

2) Save Files on the ESP32 Filesystem (SPIFFS or LittleFS)

The ESP32 contains a Serial Peripheral Interface Flash File System (SPIFFS). SPIFFS is a lightweight filesystem created for microcontrollers with a flash chip (like the ESP32) that allows you to store files in the flash memory. Here are some tutorials using the ESP32 filesystem: Install ESP32 Filesystem Uploader in Arduino IDE ESP32 Web Server using SPIFFS (SPI Flash File System) SPIFFS lets you access the flash memory like you would do in a normal filesystem in your computer, but simpler and more limited. You can read, write, close, and delete files. The SPIFFS library that allows you to interact with the flash chip doesn't support directories, so everything is saved on a flat structure. LittleFS is a new filesystem that you can use with the ESP32 that supports directories, is faster, and has some other improvements over SPIFFS. You can create files to save data on the ESP32 filesystem as you would do with a microSD card. The advantage is that you don't need any extra hardware. However, it's not appropriate for saving big amounts of data or for long-term datalogging applications and you're limited to the size dedicated to the filesystem on the ESP2 partition. Advantages of using SPIFFS or LittleFS with the ESP32 for datalogging: Easy to use: it is compatible with the FS.h library it's also used with the microSD card to create and handle files. No extra hardware needed: all ESP32 boards comes with a flash chip, so you can use SPIFFS or LittleFS straight away. Doesn't require internet connection. Disadvantages: Storage limit: the number and size of files you can save will depend on the flash chip memory size. Most will be in the range of 4MB, so the SPIFFS/LittleFS partition will be lower than that. Additonally, you can't use all SPIFFS partition memory for storage, only about 75%check the docs. Not suitable for long term datalogging or very frequent writes: read and write to the filesystem might become slower as the available memory decreases. Not easy to visualize your data: if you need to access all your data to process it later, you'll need to write a sketch that gets all data saved in spiffs, or a server that provides access to the filesystem files.

3) ESP32 Save Data to the Firebase Realtime Database

Firebase is Google's mobile application development platform that provides several tools to save data: Realtime Database: realtime, cloud-hosted, NoSQL database; data is stored in a JSON structure. Cloud Firestore: realtime, cloud-hosted, NoSQL database; data is stored in documents. Cloud Storage: scalable file storage to upload and download files. After saving your data on Firebase, you can access all the data online by going to your Firebase console. Additionally, you can easily interact with their storage and database services using javascript and other programming languages, so you can create your own web application to display that data online as we do in our Firebase Web App with the ESP32 and ESP8266 eBook. We prefer using the Realtime Database for datalogging projects, because, in our opinion, it's easier to handle and program than Cloud Firestore. We have the following guides to learn how to get started datalogging using Firebase Realtime Database. ESP32: Getting Started with Firebase (Realtime Database) ESP32 Data Logging to Firebase Realtime Database ESP32/ESP8266 Firebase: Send BME280 Sensor Readings to the Realtime Database Firebase Cloud Storage is another service that can be used to save files. Imagine that you have several files saved on the microSD card. The ESP32 can connect once a day to the internet to backup those files on Firebase Cloud Storage. We also have used Firebase Cloud Storage to store pictures taken with the ESP32-CAM: ESP32-CAM Save Picture in Firebase Storage Advantages of using Firebase Realtime Database with the ESP32 for datalogging: Access from anywhere: after submitting the data, it is available online and you can check it from anywhere in the world; Store and retrieve data in real-time: the data submitted to the Firebase Realtime Database synchronizes in real-time with other devices and services (if you have several boards or a web app listening to that database, it receives the changes almost instantly); Free usage for small projects: for personal projects, you can use Firebase tools for free until a predefined storage limit; Flexibility: It's great if you want to build a web app for your IoT projects and you need to save different types of data; Users and authentication: Firebase services provide tools to easily handle users and authentication. Disadvantages: Might be difficult to get started: You need to set up a Firebase project with authentication methods, database rules, and other settings that might be difficult for beginners (but if you follow our Firebase eBook, you'll get everything set up in no time); Requires internet connection: the ESP32 needs to be connected to the internet to connect with Firebase services, if the ESP32 is used in an environment without internet access, it will not be able to communicate with Firebase. Additional costs: Firebase has a free plan that's usually more than enough for personal applications, but if you need more data storage, you'll need to get a paid plan. However, the free plan was always more than enough for our projects.

4) InfluxDB Time Series Database

InfluxDB is one of my favorite databases for datalogging. InfluxDB is an open-source high-performance time series database (TSDB) that can store large amounts of data per second. Each data point you submit to the database is associated with a particular timestamp. So, it is ideal for IoT datalogging projects like storing data from your weather station sensors. You can run InfluxDB in InfluxDB Cloud, or locally on your laptop or Raspberry Pi. InfluxDB cloud is a great alternative because it hosts your data that can be accessed from anywhere. However, the free cloud plan only lets you save data until the last 30 days. Depending on your project application, that might be a good alternative. Or if you're willing to pay for a premium plan, InfluxDB cloud might be the best solution. To get started datalogging with InfluxDB and the ESP32, you can follow the next tutorials. ESP32: Getting Started with InfluxDB ESP32/ESP8266: Send BME280 Sensor Readings to InfluxDB Besides being a database, InfluxDB also allows you to build dashboards with different types of graphs, charts, gauges, and more to display your data. You can do all of this using their interface. You don't need to write any code to build the charts or dashboard. Advantages of using InfluxDB for Datalogging with the ESP32: It's a time-series database: each reading you submit to the database will be associated with a specific timestamp. You don't need to get the date and time on the ESP32. Dashboard with customizable charts: InfluxDB user interface provides tools for building custom dashboards to visualize your data. Without any knowledge of web programming, you can build awesome dashboards with customizable charts. It's available online: if you use InfluxDB cloud storage, you can access your data from anywhere in the world by accessing your InfluxDB account. Also available offline: if you want to have full control over your data, you can also install InfluxDB on your own cloud server, or locally on a Raspberry Pi. Disadvantages: Requires installation: you need to install and/or setup InfluxDB first, which might require an extra step on your project; Only time-based data: all data is associated with a specific timestamp, so it might not be the best database if you want to save data that doesn't require a timestamp like JSON data. Requires internet connection: the ESP32 needs to be connected to the internet to connect with InfluxDB, unless you install it locally. Retention period: the free cloud plan only lets you save data until the last 30 days.

5) Use Deta Base to Save Your Data

Deta Base is a NoSQL database. It is unlimited, free, and easy to use. Additionally, it requires minimal setup. So, it's perfect for your hobbyist projects and prototyping. It offers a UI through which you can easily see, query, update and delete records in the database. The biggest advantage of Deta Base over other database solutions is that it requires very little setup. Once you sign up for Deta Base, it's ready to use. As with Firebase, you can create your own web applications to interact with the database. If you want to learn how to use Deta Base with the ESP32, you can follow our guide: ESP32: Getting Started with Deta Base (Unlimited and Free Database for Developers) Advantages of using Deta Base with the ESP32: It's free. It requires minimal setup, once you sign up to Deta Base, you can start using it right away to store data. It's fast and scalable. You can check your data online from anywhere from your account. Disadvantages: Small community: it's still in the early stages, so if you need help, there's still little information online. Beta version: it's in beta version, and you might encounter unexpected bugs. Requires internet connection: like the other cloud solutions, the ESP32 needs to have access to the internet.

6) Use ThingSpeak Channels

ThingSpeak is an IoT platform in which you can create channels to store data. It provides visualization tools and different widgets to display your data like charts, gauges, or numeric displays. The submitted data is also associated with a timestamp, which is useful if you want to display it on charts to see how it behaves over time. You can have multiple devices publishing data to your Thingspeak account. Usually, each device will require a channel. The free plan is limited to four channels. In my opinion, the visual interface is easy to use, but it allows very little customization when compared with the tools provided by InfluxDB. However, it is perfect if you just want a simple visualization without having to worry about formatting details (like series colors, charts background colors, etc.). The data is stored in the cloud, so you can access your data anywhere from your account, We have two different tutorials that show different methods to publish data to Thingspeak. The following shows how to make an HTTP POST request with data to Thingspeak services (you need to write the request manually on the code): ESP32 HTTP POST with Arduino IDE (ThingSpeak) An easier way to publish data to Thingspeak with the ESP32 is to use a library. You just need to call a function and pass as an argument the data you want to send. ESP32 Publish Sensor Readings to ThingSpeak (easiest way) Advantages of using ThinsSpeak with the ESP32: Suitable for multiple IoT devices: you can have multiple ESP32 boards and other IoT devices publishing data to your Thinspeak account. Free to use: it provides a free plan with up to four channels. Usually, each device will require a different channel on ThingSpeak. Minimal setup: you don't need to install anything on your computer or set up any database settings; once you create an account and a new channel, it's ready to use. Simple dashboard: the data will be displayed on the widget of your choice, without having to program anything on the user interface. Disadvantages: Limited free plan: the free plan is limited to four channels, which might not be enough for your project. Little customization: the number of available widgets is limited and allows very little customization. Requires internet connection: like the other cloud solutions, the ESP32 needs to have access to the internet.

7) Save ESP32 Data to Google Sheets

Another great alternative to log data with the ESP32 is to use Google Sheets. Then, you can analyze, manage and display your data using the features provided in Google Sheets. You can have access to your data from anywhere using your Google account. This is a good method if you need to store data that needs processing later on. We have a guide showing how to log sensor readings to Google Sheets using IFTTT services. The advantage of using IFTTT services is that you don't need to create any scripts or additional configurations in your Google account. However, it has some limitations in the number of requests you can make with the free account and with how much information you can save at once. Here's the tutorial: ESP32 Publish Sensor Readings to Google Sheets If you don't want to rely on a third-party service to publish data to google sheets with the ESP32, there's currently a library to do that. Check the Arduino Google Sheet Client Library for ESP8266 and ESP32. Advantages of saving ESP32 data to Google Sheets: Easy data management: if you need to process your data after collecting it, you can use all the functionalities of google sheets to process data. Access from anywhere: you can easily access your data from your google account. Shareable: you can share the google sheet file with other people by providing access to their specific email. Disadvantages: It doesn't display the data visually automatically. You need to know how to create charts and graphs (which is not difficult, so I don't think this is a big limitation). If using a third-party service like IFTTT (free account) you're limited to the number of requests and fields you can pass at a time. Requires internet connection: like the other cloud solutions, the ESP32 needs to have access to the internet.

8) ESP32 Save Data to MySQL Database on a Cloud Server

You can install a MySQL Database on a cloud server and then, make requests with the ESP32 to publish and retrieve data. This method is great for those who like programming, setting up their own servers, and want to have full control over their data storage. If you're already familiar with MySQL and PHP, you'll certainly like this method. The biggest disadvantage of this method is that you need to set up everything on your own from scratch which is easily prone to errors. Additionally, you'll need to pay for hosting and a domain name. You can follow the next tutorials to set up a MySQL database on your own server and then, learn how to create a web page to display the data on charts:
    ESP32/ESP8266 Insert Data into MySQL Database using PHP and Arduino IDE Visualize Your Sensor Readings from Anywhere in the World (ESP32/ESP8266 + MySQL + PHP)

9) MySQL Database on a Local Server (Raspberry Pi)

This is an alternative to the previous solution. If you want to set up a MySQL database locally on a Raspberry Pi to save the data coming from the ESP32, you'll need to follow the next tutorial first: Raspberry Pi: Install Apache + MySQL + PHP (LAMP Server) Then, you can follow the next tutorial to learn how to actually publish data with the ESP32 to your local server and how to display it on a web page: ESP32/ESP8266 Publish Data to Raspberry Pi LAMP Server

Wrapping Up

In this tutorial, we compiled different methods that you can use to save and log data permanently using your ESP32. Depending on your expertise level and project requirements, one solution might be more suitable than the others. There are many other methods besides the ones we covered here. We created a compilation of the tutorials we already have about this subject. We hope you found this compilation guide useful. Let us know what's your favorite storage method and for which applications you use it.

CAM Post Images to Local or Cloud Server using PHP (Photo Manager)

Learn how to make HTTP POST requests using the ESP32-CAM board with Arduino IDE to send photos to a server. We'll show how to post a JPG/JPEG image to a local server (Raspberry Pi LAMP server) or to a cloud server (that you can access from anywhere). The photos will be displayed in a gallery where you can view or delete the photos. To save the images in the server and create the gallery, we'll use PHP scripts. Updated on 27 March 2023 To build this project, you need to follow the next steps. Follow the LAMP Server or the Hosting Server instructions depending on if you want to access the photos locally or from anywhere.
    Hosting your PHP Application
      Raspberry Pi LAMP Server (local access) Hosting Server (access from anywhere)
    PHP scripts to save and display photos in the server
      Raspberry Pi LAMP Server (local access) Hosting Server (access from anywhere)
    Program the ESP32-CAM with Arduino IDE Testing and Final Demonstration

1. Hosting Your PHP Application

The goal of this project is to have a local or cloud server to store and access your ESP32-CAM photos. 1. Raspberry Pi local server: With a Raspberry Pi LAMP server, you can access your images locally (as illustrated below). You can run a LAMP (Linux, Apache, MySQL, PHP) server on a Raspberry Pi to access data in your local network. Raspberry Pi LAMP Server: Local Linux server that you use to access your images locally. Setup Local RPi LAMP Server 2. Cloud server (Bluehost hosting solution) You also can visualize the ESP32-CAM photos from anywhere in the world by accessing your own server + domain. Here's a high level overview on how it works: Bluehost (user-friendly with cPanel): free domain name when you sign up for the 3-year plan. I recommend choosing the unlimited websites option; Note that any hosting service that offers PHP will work with this tutorial. If you don't have a hosting account, I recommend signing up for Bluehost. Get Hosting and Domain Name with Bluehost When buying a hosting account, you'll also have to purchase a domain name. This is what makes this project interesting: you'll be able to go your domain name (http://example.com) and see your ESP32-CAM photos. If you like our projects, you might consider signing up to Bluehost, because you'll be supporting our work.

HTTP POST Request Method

The Hypertext Transfer Protocol (HTTP) works as a request-response protocol between a client and server. Here's an example: The ESP32 (client) submits an HTTP request to a Server (for example: local RPi Lamp Server or example.com); The server returns a response to the ESP32 (client); HTTP POST is used to send data to a server to create/update a resource. For example, publish an image to a server. POST /upload.php HTTP/1.1 Host: example.com Content-Type: image/jpeg

2.1. Preparing Your .php Files and uploads Folder (Raspberry Pi LAMP Server)

This section prepares your .php files and uploads folder for your Raspberry Pi LAMP Server. If you're using your own server + domain name, skip to the next section. Having a Raspberry Pi running Apache and PHP, in the Raspberry Pi board terminal window navigate to the /var/www/html/ directory: pi@raspberrypi:~ $ cd /var/www/html/ Create a new folder called uploads: pi@raspberrypi:/var/www/html $ mkdir uploads pi@raspberrypi:/var/www/html $ ls uploads At the moment, /var/www/html is owned by root, use the next commands to change to the pi user and give it all permissions so that you can save photos using a PHP script later on. sudo chown -R pi:pi /var/www/html chmod -R 777 /var/www/html/ Finally, create a new upload.php file: pi@raspberrypi:/var/www/html $ nano upload.php This PHP script is responsible for receiving incoming images from the ESP32-CAM, rename the images with a timestamp and store them in the uploads folder. Edit the newly created file (upload.php) and copy the following snippet: <?php // Rui Santos // Complete project details at https://RandomNerdTutorials.com/esp32-cam-post-image-photo-server/ // Code Based on this example: w3schools.com/php/php_file_upload.asp $target_dir = "uploads/"; $datum = mktime(date('H')+0, date('i'), date('s'), date('m'), date('d'), date('y')); $target_file = $target_dir . date('Y.m.d_H:i:s_', $datum) . basename($_FILES["imageFile"]["name"]); $uploadOk = 1; $imageFileType = strtolower(pathinfo($target_file,PATHINFO_EXTENSION)); // Check if image file is a actual image or fake image if(isset($_POST["submit"])) { $check = getimagesize($_FILES["imageFile"]["tmp_name"]); if($check !== false) { echo "File is an image - " . $check["mime"] . "."; $uploadOk = 1; } else { echo "File is not an image."; $uploadOk = 0; } } // Check if file already exists if (file_exists($target_file)) { echo "Sorry, file already exists."; $uploadOk = 0; } // Check file size if ($_FILES["imageFile"]["size"] > 500000) { echo "Sorry, your file is too large."; $uploadOk = 0; } // Allow certain file formats if($imageFileType != "jpg" && $imageFileType != "png" && $imageFileType != "jpeg" && $imageFileType != "gif" ) { echo "Sorry, only JPG, JPEG, PNG & GIF files are allowed."; $uploadOk = 0; } // Check if $uploadOk is set to 0 by an error if ($uploadOk == 0) { echo "Sorry, your file was not uploaded."; // if everything is ok, try to upload file } else { if (move_uploaded_file($_FILES["imageFile"]["tmp_name"], $target_file)) { echo "The file ". basename( $_FILES["imageFile"]["name"]). " has been uploaded."; } else { echo "Sorry, there was an error uploading your file."; } } ?> View raw code Your upload.php file should look like this. Save your file and exit (Ctrl+X, Y, and Enter key): Then, create a new gallery.php file: pi@raspberrypi:/var/www/html $ nano gallery.php Edit the newly created file (gallery.php) and copy the following snippet: <!-- Rui Santos Complete project details at https://RandomNerdTutorials.com/esp32-cam-post-image-photo-server/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. --> <!DOCTYPE html> <html> <head> <title>ESP32-CAM Photo Gallery</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> .flex-container { display: flex; flex-wrap: wrap; } .flex-container > div { text-align: center; margin: 10px; } </style> </head><body> <h2>ESP32-CAM Photo Gallery</h2> <?php // Image extensions $image_extensions = array("png","jpg","jpeg","gif"); // Check delete HTTP GET request - remove images if(isset($_GET["delete"])){ $imageFileType = strtolower(pathinfo($_GET["delete"],PATHINFO_EXTENSION)); if (file_exists($_GET["delete"]) && ($imageFileType == "jpg" || $imageFileType == "png" || $imageFileType == "jpeg") ) { echo "File found and deleted: " . $_GET["delete"]; unlink($_GET["delete"]); } else { echo 'File not found - <a href="gallery.php">refresh</a>'; } } // Target directory $dir = 'uploads/'; if (is_dir($dir)){ echo '<div>'; $count = 1; $files = scandir($dir); rsort($files); foreach ($files as $file) { if ($file != '.' && $file != '..') {?> <div> <p><a href="gallery.php?delete=<?php echo $dir . $file; ?>">Delete file</a> - <?php echo $file; ?></p> <a href="<?php echo $dir . $file; ?>"> <img src="<?php echo $dir . $file; ?>" style="width: 350px;" alt="" title=""/> </a> </div> <?php $count++; } } } if($count==1) { echo "<p>No images found</p>"; } ?> </div> </body> </html> View raw code This PHP script is responsible for displaying the images on the gallery. Your gallery.php file should look like this. Save your file and exit (Ctrl+X, Y, and Enter key):

2.2. Preparing Your .php Files and uploads Folder (Hosting Service)

If you prefer to run your server remotely and access the photos from anywhere, you need a hosting account. After signing up for a hosting account and setting up a domain name, you can login to your cPanel or similar dashboard. After that, open the File Manager. Open the Advanced tab and select File Manager: Then, select the public_html option. Press the + File button to create a new upload.php file and a new gallery.php file. Then, click the +Folder button to create the Uploads folder. With the three items created, edit the upload.php file: This PHP script is responsible for receiving incoming images from the ESP32-CAM, rename the images with a timestamp and store them in the uploads folder. Edit the newly created file (upload.php) and copy the following snippet: <?php // Rui Santos // Complete project details at https://RandomNerdTutorials.com/esp32-cam-post-image-photo-server/ // Code Based on this example: w3schools.com/php/php_file_upload.asp $target_dir = "uploads/"; $datum = mktime(date('H')+0, date('i'), date('s'), date('m'), date('d'), date('y')); $target_file = $target_dir . date('Y.m.d_H:i:s_', $datum) . basename($_FILES["imageFile"]["name"]); $uploadOk = 1; $imageFileType = strtolower(pathinfo($target_file,PATHINFO_EXTENSION)); // Check if image file is a actual image or fake image if(isset($_POST["submit"])) { $check = getimagesize($_FILES["imageFile"]["tmp_name"]); if($check !== false) { echo "File is an image - " . $check["mime"] . "."; $uploadOk = 1; } else { echo "File is not an image."; $uploadOk = 0; } } // Check if file already exists if (file_exists($target_file)) { echo "Sorry, file already exists."; $uploadOk = 0; } // Check file size if ($_FILES["imageFile"]["size"] > 500000) { echo "Sorry, your file is too large."; $uploadOk = 0; } // Allow certain file formats if($imageFileType != "jpg" && $imageFileType != "png" && $imageFileType != "jpeg" && $imageFileType != "gif" ) { echo "Sorry, only JPG, JPEG, PNG & GIF files are allowed."; $uploadOk = 0; } // Check if $uploadOk is set to 0 by an error if ($uploadOk == 0) { echo "Sorry, your file was not uploaded."; // if everything is ok, try to upload file } else { if (move_uploaded_file($_FILES["imageFile"]["tmp_name"], $target_file)) { echo "The file ". basename( $_FILES["imageFile"]["name"]). " has been uploaded."; } else { echo "Sorry, there was an error uploading your file."; } } ?> View raw code Save your file and exit. Then, edit the gallery.php file and copy the following snippet. This is responsible for displaying the images in the gallery. <!-- Rui Santos Complete project details at https://RandomNerdTutorials.com/esp32-cam-post-image-photo-server/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. --> <!DOCTYPE html> <html> <head> <title>ESP32-CAM Photo Gallery</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> .flex-container { display: flex; flex-wrap: wrap; } .flex-container > div { text-align: center; margin: 10px; } </style> </head><body> <h2>ESP32-CAM Photo Gallery</h2> <?php // Image extensions $image_extensions = array("png","jpg","jpeg","gif"); // Check delete HTTP GET request - remove images if(isset($_GET["delete"])){ $imageFileType = strtolower(pathinfo($_GET["delete"],PATHINFO_EXTENSION)); if (file_exists($_GET["delete"]) && ($imageFileType == "jpg" || $imageFileType == "png" || $imageFileType == "jpeg") ) { echo "File found and deleted: " . $_GET["delete"]; unlink($_GET["delete"]); } else { echo 'File not found - <a href="gallery.php">refresh</a>'; } } // Target directory $dir = 'uploads/'; if (is_dir($dir)){ echo '<div>'; $count = 1; $files = scandir($dir); rsort($files); foreach ($files as $file) { if ($file != '.' && $file != '..') {?> <div> <p><a href="gallery.php?delete=<?php echo $dir . $file; ?>">Delete file</a> - <?php echo $file; ?></p> <a href="<?php echo $dir . $file; ?>"> <img src="<?php echo $dir . $file; ?>" style="width: 350px;" alt="" title=""/> </a> </div> <?php $count++; } } } if($count==1) { echo "<p>No images found</p>"; } ?> </div> </body> </html> View raw code Save your file and exit. That's it! Your server is ready.

3. ESP32-CAM HTTP Post Images/Photos to Server

Now that you have your server ready (Raspberry Pi LAMP server or cloud server), it's time to prepare the ESP32-CAM with the code to publish a new image to your server every 30 seconds. Before proceeding with this tutorial, make sure you complete the following prerequisites.

Parts Required

To follow this tutorial you need the following components: ESP32-CAM with OV2640 read Best ESP32-CAM Dev Boards FTDI programmer Female-to-female jumper wires 5V power supply for ESP32-CAM Local server: Raspberry Pi Board read Best Raspberry Pi Starter Kits MicroSD Card 32GB Class10 Raspberry Pi Power Supply (5V 2.5A) Cloud server (alternative): Bluehost You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Arduino IDE

We'll program the ESP32-CAM using Arduino IDE, so make sure you have the ESP32 add-on installed. Installing the ESP32 Board in Arduino IDE (Windows, Mac OS X, Linux)

Check the PHP URL

You should try to open the Raspberry Pi local IP address or your external example.com domain name, followed by /upload.php that should return: Sorry, only JPG, JPEG, PNG & GIF files are allowed.Sorry, your file was not uploaded. If you see that message save your URL/domain name and path, your server should be ready and you can continue with this guide. Additionally, try to access the /gallery.php path. You should get something as shown below:

ESP32-CAM Code

If you're using a local server without TLS/SSL, or a cloud server that doesn't support HTTPS, use the HTTP POST Request Code. If you're using a cloud server that requires HTTPS requests, use this code instead: HTTPS POST Request Code.

ESP32-CAM HTTP POST Request

The next sketch posts the image to a server using HTTP POST. Copy the code below to your Arduino IDE. /* Rui Santos Complete project details at https://RandomNerdTutorials.com/esp32-cam-post-image-photo-server/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ #include <Arduino.h> #include <WiFi.h> #include "soc/soc.h" #include "soc/rtc_cntl_reg.h" #include "esp_camera.h" const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; String serverName = "192.168.1.XXX"; // REPLACE WITH YOUR Raspberry Pi IP ADDRESS //String serverName = "example.com"; // OR REPLACE WITH YOUR DOMAIN NAME String serverPath = "/upload.php"; // The default serverPath should be upload.php const int serverPort = 80; WiFiClient client; // CAMERA_MODEL_AI_THINKER #define PWDN_GPIO_NUM 32 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 0 #define SIOD_GPIO_NUM 26 #define SIOC_GPIO_NUM 27 #define Y9_GPIO_NUM 35 #define Y8_GPIO_NUM 34 #define Y7_GPIO_NUM 39 #define Y6_GPIO_NUM 36 #define Y5_GPIO_NUM 21 #define Y4_GPIO_NUM 19 #define Y3_GPIO_NUM 18 #define Y2_GPIO_NUM 5 #define VSYNC_GPIO_NUM 25 #define HREF_GPIO_NUM 23 #define PCLK_GPIO_NUM 22 const int timerInterval = 30000; // time between each HTTP POST image unsigned long previousMillis = 0; // last time image was sent void setup() { WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); Serial.begin(115200); WiFi.mode(WIFI_STA); Serial.println(); Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(500); } Serial.println(); Serial.print("ESP32-CAM IP Address: "); Serial.println(WiFi.localIP()); camera_config_t config; config.ledc_channel = LEDC_CHANNEL_0; config.ledc_timer = LEDC_TIMER_0; config.pin_d0 = Y2_GPIO_NUM; config.pin_d1 = Y3_GPIO_NUM; config.pin_d2 = Y4_GPIO_NUM; config.pin_d3 = Y5_GPIO_NUM; config.pin_d4 = Y6_GPIO_NUM; config.pin_d5 = Y7_GPIO_NUM; config.pin_d6 = Y8_GPIO_NUM; config.pin_d7 = Y9_GPIO_NUM; config.pin_xclk = XCLK_GPIO_NUM; config.pin_pclk = PCLK_GPIO_NUM; config.pin_vsync = VSYNC_GPIO_NUM; config.pin_href = HREF_GPIO_NUM; config.pin_sscb_sda = SIOD_GPIO_NUM; config.pin_sscb_scl = SIOC_GPIO_NUM; config.pin_pwdn = PWDN_GPIO_NUM; config.pin_reset = RESET_GPIO_NUM; config.xclk_freq_hz = 20000000; config.pixel_format = PIXFORMAT_JPEG; // init with high specs to pre-allocate larger buffers if(psramFound()){ config.frame_size = FRAMESIZE_SVGA; config.jpeg_quality = 10; //0-63 lower number means higher quality config.fb_count = 2; } else { config.frame_size = FRAMESIZE_CIF; config.jpeg_quality = 12; //0-63 lower number means higher quality config.fb_count = 1; } // camera init esp_err_t err = esp_camera_init(&config); if (err != ESP_OK) { Serial.printf("Camera init failed with error 0x%x", err); delay(1000); ESP.restart(); } sendPhoto(); } void loop() { unsigned long currentMillis = millis(); if (currentMillis - previousMillis >= timerInterval) { sendPhoto(); previousMillis = currentMillis; } } String sendPhoto() { String getAll; String getBody; camera_fb_t * fb = NULL; fb = esp_camera_fb_get(); if(!fb) { Serial.println("Camera capture failed"); delay(1000); ESP.restart(); } Serial.println("Connecting to server: " + serverName); if (client.connect(serverName.c_str(), serverPort)) { Serial.println("Connection successful!"); String head = "--RandomNerdTutorials\r\nContent-Disposition: form-data; name=\"imageFile\"; filename=\"esp32-cam.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n"; String tail = "\r\n--RandomNerdTutorials--\r\n"; uint32_t imageLen = fb->len; uint32_t extraLen = head.length() + tail.length(); uint32_t totalLen = imageLen + extraLen; client.println("POST " + serverPath + " HTTP/1.1"); client.println("Host: " + serverName); client.println("Content-Length: " + String(totalLen)); client.println("Content-Type: multipart/form-data; boundary=RandomNerdTutorials"); client.println(); client.print(head); uint8_t *fbBuf = fb->buf; size_t fbLen = fb->len; for (size_t n=0; n<fbLen; n=n+1024) { if (n+1024 < fbLen) { client.write(fbBuf, 1024); fbBuf += 1024; } else if (fbLen%1024>0) { size_t remainder = fbLen%1024; client.write(fbBuf, remainder); } } client.print(tail); esp_camera_fb_return(fb); int timoutTimer = 10000; long startTimer = millis(); boolean state = false; while ((startTimer + timoutTimer) > millis()) { Serial.print("."); delay(100); while (client.available()) { char c = client.read(); if (c == '\n') { if (getAll.length()==0) { state=true; } getAll = ""; } else if (c != '\r') { getAll += String(c); } if (state==true) { getBody += String(c); } startTimer = millis(); } if (getBody.length()>0) { break; } } Serial.println(); client.stop(); Serial.println(getBody); } else { getBody = "Connection to " + serverName + " failed."; Serial.println(getBody); } return getBody; } View raw code

ESP32-CAM HTTPS POST Request

The next sketch posts the image to a server using HTTPS POST. Copy the code below to your Arduino IDE. /* Rui Santos Complete project details at: https://RandomNerdTutorials.com/esp32-cam-http-post-php-arduino/ https://RandomNerdTutorials.com/esp32-cam-post-image-photo-server/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ #include <Arduino.h> #include <WiFi.h> #include <WiFiClientSecure.h> #include "soc/soc.h" #include "soc/rtc_cntl_reg.h" #include "esp_camera.h" const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; String serverName = "example.com"; //REPLACE WITH YOUR DOMAIN NAME String serverPath = "/upload.php"; // The default serverPath should be upload.php const int serverPort = 443; //server port for HTTPS WiFiClientSecure client; // CAMERA_MODEL_AI_THINKER #define PWDN_GPIO_NUM 32 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 0 #define SIOD_GPIO_NUM 26 #define SIOC_GPIO_NUM 27 #define Y9_GPIO_NUM 35 #define Y8_GPIO_NUM 34 #define Y7_GPIO_NUM 39 #define Y6_GPIO_NUM 36 #define Y5_GPIO_NUM 21 #define Y4_GPIO_NUM 19 #define Y3_GPIO_NUM 18 #define Y2_GPIO_NUM 5 #define VSYNC_GPIO_NUM 25 #define HREF_GPIO_NUM 23 #define PCLK_GPIO_NUM 22 const int timerInterval = 30000; // time between each HTTP POST image unsigned long previousMillis = 0; // last time image was sent void setup() { WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); Serial.begin(115200); WiFi.mode(WIFI_STA); Serial.println(); Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(500); } Serial.println(); Serial.print("ESP32-CAM IP Address: "); Serial.println(WiFi.localIP()); camera_config_t config; config.ledc_channel = LEDC_CHANNEL_0; config.ledc_timer = LEDC_TIMER_0; config.pin_d0 = Y2_GPIO_NUM; config.pin_d1 = Y3_GPIO_NUM; config.pin_d2 = Y4_GPIO_NUM; config.pin_d3 = Y5_GPIO_NUM; config.pin_d4 = Y6_GPIO_NUM; config.pin_d5 = Y7_GPIO_NUM; config.pin_d6 = Y8_GPIO_NUM; config.pin_d7 = Y9_GPIO_NUM; config.pin_xclk = XCLK_GPIO_NUM; config.pin_pclk = PCLK_GPIO_NUM; config.pin_vsync = VSYNC_GPIO_NUM; config.pin_href = HREF_GPIO_NUM; config.pin_sscb_sda = SIOD_GPIO_NUM; config.pin_sscb_scl = SIOC_GPIO_NUM; config.pin_pwdn = PWDN_GPIO_NUM; config.pin_reset = RESET_GPIO_NUM; config.xclk_freq_hz = 20000000; config.pixel_format = PIXFORMAT_JPEG; // init with high specs to pre-allocate larger buffers if(psramFound()){ config.frame_size = FRAMESIZE_SVGA; config.jpeg_quality = 10; //0-63 lower number means higher quality config.fb_count = 2; } else { config.frame_size = FRAMESIZE_CIF; config.jpeg_quality = 12; //0-63 lower number means higher quality config.fb_count = 1; } // camera init esp_err_t err = esp_camera_init(&config); if (err != ESP_OK) { Serial.printf("Camera init failed with error 0x%x", err); delay(1000); ESP.restart(); } sendPhoto(); } void loop() { unsigned long currentMillis = millis(); if (currentMillis - previousMillis >= timerInterval) { sendPhoto(); previousMillis = currentMillis; } } String sendPhoto() { String getAll; String getBody; camera_fb_t * fb = NULL; fb = esp_camera_fb_get(); if(!fb) { Serial.println("Camera capture failed"); delay(1000); ESP.restart(); } Serial.println("Connecting to server: " + serverName); client.setInsecure(); //skip certificate validation if (client.connect(serverName.c_str(), serverPort)) { Serial.println("Connection successful!"); String head = "--RandomNerdTutorials\r\nContent-Disposition: form-data; name=\"imageFile\"; filename=\"esp32-cam.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n"; String tail = "\r\n--RandomNerdTutorials--\r\n"; uint32_t imageLen = fb->len; uint32_t extraLen = head.length() + tail.length(); uint32_t totalLen = imageLen + extraLen; client.println("POST " + serverPath + " HTTP/1.1"); client.println("Host: " + serverName); client.println("Content-Length: " + String(totalLen)); client.println("Content-Type: multipart/form-data; boundary=RandomNerdTutorials"); client.println(); client.print(head); uint8_t *fbBuf = fb->buf; size_t fbLen = fb->len; for (size_t n=0; n<fbLen; n=n+1024) { if (n+1024 < fbLen) { client.write(fbBuf, 1024); fbBuf += 1024; } else if (fbLen%1024>0) { size_t remainder = fbLen%1024; client.write(fbBuf, remainder); } } client.print(tail); esp_camera_fb_return(fb); int timoutTimer = 10000; long startTimer = millis(); boolean state = false; while ((startTimer + timoutTimer) > millis()) { Serial.print("."); delay(100); while (client.available()) { char c = client.read(); if (c == '\n') { if (getAll.length()==0) { state=true; } getAll = ""; } else if (c != '\r') { getAll += String(c); } if (state==true) { getBody += String(c); } startTimer = millis(); } if (getBody.length()>0) { break; } } Serial.println(); client.stop(); Serial.println(getBody); } else { getBody = "Connection to " + serverName + " failed."; Serial.println(getBody); } return getBody; } View raw code

Inserting your Network Credentials, Camera, and Server Details

Before uploading the code, you need to insert your network credentials in the following variables: const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; Make sure you select the right camera module. In this case, we're using the AI-THINKER Model. If you're using another camera model, you can read this Guide ESP32-CAM Camera Boards: Pin and GPIOs Assignment. Add your Raspberry Pi IP address or use the server domain name: String serverName = "192.168.1.XXX"; // REPLACE WITH YOUR Raspberry Pi IP ADDRESS //String serverName = "example.com"; // OR REPLACE WITH YOUR DOMAIN NAME String serverPath = "/upload.php"; // The default serverPath should be upload.php

Upload Code to ESP32-CAM

Now you can upload the code to your ESP32-CAM board. Connect the ESP32-CAM board to your computer using an FTDI programmer. Follow the next schematic diagram: Many FTDI programmers have a jumper that allows you to select 3.3V or 5V. Make sure the jumper is in the right place to select 5V. Important: GPIO 0 needs to be connected to GND so that you're able to upload code.
ESP32-CAM FTDI Programmer
GND GND
5V VCC (5V)
U0R TX
U0T RX
GPIO 0 GND
To upload the code, follow the next steps:
    Go to Tools > Board and select AI-Thinker ESP32-CAM. Go to Tools > Port and select the COM port the ESP32 is connected to. Then, click the upload button to upload the code. When you start to see these dots on the debugging window as shown below, press the ESP32-CAM on-board RST button.
After a few seconds, the code should be successfully uploaded to your board. If you have troubles uploading the code, read our ESP32-CAM Troubleshooting Guide.

How the Code Works

Here's a quick explanation of how the code works: Imports all libraries; Defines the needed variables; Defines the camera pins; In the setup() you establish a Wi-Fi connection and initialize the ESP32 camera. The loop() has a timer that calls the sendPhoto() function every 30 seconds. You can change that delay time in the timerInterval variable. The sendPhoto() function is the part that actually takes a photo and sends it to your server. You can use that function in other of your projects that require taking and publishing a photo to a server.

4. Testing and Final Demonstration

After uploading the code to your board, open the Arduino IDE Serial Monitor and you should see a similar message being printed every 30 seconds: The file esp32-cam.jpg has been uploaded. If you go to your local server URL http://IP-Address/uploads, or to your cloud server URL https://example.com/uploads you should have a folder with all your stored photos. You can open each link to open a new page with the full image: Now, if you go to your local server URL http://IP-Address/gallery.php, or to your cloud server URL https://example.com/gallery.php, you can access the gallery page, where you can view and delete the photos. To delete any photo, just click on the Delete file link next to each image.

Wrapping Up

That's it! Now, you can send your ESP32-CAM photos to any server using HTTP POST. Modify this project to best suit your needs. For example, take a photo and send to a server when motion is detected.

Control ESP32 and ESP8266 GPIOs from Anywhere in the World

In this project, you'll learn how to control your ESP32 or ESP8266 GPIOs from anywhere in the world. This can be very useful to control a relay, a thermostat, or any other device remotely. Updated on 27 March 2023 This project is also very versatile. Through your cloud dashboard, you can easily control more outputs (without uploading new code to your board) and you can even connect multiple boards to your server. Previously, we've stored sensor readings into a database and we've used different methods to display sensor readings on a: Table Charts Gauges Now, I've created this new project where you can create buttons in a dashboard and assign them to a Board and GPIO number. Then, you can use the toggle switches to control the ESP32 or ESP8266 outputs from anywhere. There are many ways of controlling outputs from anywhere, and even though this is a working solution there are other methods that provide a two-way communication with your devices. I also recommend that you take this project further and add more features to fit your own needs. To build this project, you'll use these technologies: ESP32 or ESP8266 programmed with Arduino IDE Hosting server and domain name PHP scripts to store and retrieve the output states stored in a MySQL database Table of Contents This project is divided into the following main sections:
    Hosting Your PHP Application and MySQL Database Preparing Your MySQL Database Creating Your Dashboard Files PHP Script Update and Retrieve Output States PHP Script for Database Functions PHP Script Control Buttons Setting Up the ESP32 or ESP8266

Watch the Video Demonstration

To see how the project works, you can watch the following video demonstration:

0. Download Source Code

For this project, you'll need these files: SQL query to create your table: Outputs_and_Boards_Table.sql Insert and access database: esp-database.php Handle HTTP requests: esp-outputs-action.php CSS file to style your web page: esp-style.css Display your control buttons: esp-outputs.php Arduino Sketch for ESP32 (with HTTPS): ESP32_HTTPS_GET_Request_JSON.ino Arduino Sketch for ESP8266 (with HTTPS): ESP8266_HTTPS_GET_Request_JSON.ino Arduino Sketch for ESP32 (without HTTPS): ESP32_HTTP_GET_Request_JSON.ino Arduino Sketch for ESP8266 (without HTTPS): ESP8266_HTTP_GET_Request_JSON.ino Download all projects files

1. Hosting Your PHP Application and MySQL Database

The goal of this project is to have your own domain name and hosting account that allows you to control your ESP32 or ESP8266 GPIOs from anywhere in the world. Here's a high-level overview of how the project works:
    You have a web page running a PHP script with some toggle buttons that allow you to control the outputs on and off; When you press the buttons, it updates the output state and saves it in your database; You can add more buttons or delete them from your dashboard; Then, you can have an ESP32 or ESP8266 or even multiple boards that make HTTP GET requests every X number of seconds to your server; Finally, according to the result of that HTTP GET request, the ESP board updates its GPIOs accordingly.

Hosting Services

I recommend using one of the following hosting services that can handle all the project requirements: Bluehost (user-friendly with cPanel): free domain name when you sign up for the 3-year plan. I recommend choosing the unlimited websites option; Digital Ocean: Linux server that you manage through a command line. I only recommended this option for advanced users. Those two services are the ones that I use and personally recommend, but you can use any other hosting service. Any hosting service that offers PHP and MySQL will work with this tutorial. If you don't have a hosting account, I recommend signing up for Bluehost. Get Hosting and Domain Name with Bluehost When buying a hosting account, you'll also have to purchase a domain name. This is what makes this project interesting: you'll be able to go your domain name (http://example.com) and control your boards. If you like our projects, you might consider signing up for one of the recommended hosting services, because you'll be supporting our work. Note: you can also run a LAMP (Linux, Apache, MySQL, PHP) server on a Raspberry Pi to control your boards in your local network. However, the purpose of this tutorial is to control the ESP outputs with your own domain name that you can access from anywhere in the world.

2. Preparing Your MySQL Database

After signing up for a hosting account and setting up a domain name, you can login to your cPanel or similar dashboard. After that, follow the next steps to create your database, username, password and SQL table.

Creating a database and user

Open the Advanced tab: 1. Type database in the search bar and select MySQL Database Wizard. 2. Enter your desired Database name. In my case, the database name is esp_data. Then, press the Next Step button: Note: later you'll have to use the database name with the prefix that your host gives you (my database prefix in the screenshot above is blurred). I'll refer to it as example_esp_data from now on. 3. Type your Database username and set a password. You must save all those details, because you'll need them later to establish a database connection with your PHP code. That's it! Your new database and user were created successfully. Now, save all your details because you'll need them later: Database name: example_esp_data Username: example_esp_board Password: your password

Creating a SQL table

After creating your database and user, go back to cPanel dashboard and search for phpMyAdmin. In the left sidebar, select your database name example_esp_data and open the SQL tab. Important: make sure you've opened the example_esp_data database. Then, click the SQL tab. If you don't follow these exact steps and run the SQL query, you might create a table in the wrong database. Copy the SQL query in the following snippet: CREATE TABLE Outputs ( id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY, name VARCHAR(64), board INT(6), gpio INT(6), state INT(6) ); INSERT INTO `Outputs`(`name`, `board`, `gpio`, `state`) VALUES ("Built-in LED", 1, 2, 0); CREATE TABLE Boards ( id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY, board INT(6), last_request TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); INSERT INTO `Boards`(`board`) VALUES (1); View raw code Paste it in the SQL query field (highlighted with a red rectangle) and press the Go button to create your table: After that, you should see your newly created tables called Boards and Outputs in the example_esp_data database as shown in the figure below:

3. Creating Your Dashboard Files

In this section, we're going to create the files that are responsible for creating your Dashboard. Here are the files: Insert and access database: esp-database.php Handle HTTP requests: esp-outputs-action.php CSS file to style your web page: esp-style.css Display your control buttons: esp-outputs.php If you're using a hosting provider with cPanel, you can search for File Manager: ESP32 ESP8266 CPanel Open Edit PHP Files with File Manager Then, select the public_html option and press the + File button to create a new file. Note: if you're following this tutorial and you're not familiar with PHP, I recommend creating these exact files. Create four new files in /public_html with these exact names and extensions: esp-database.php esp-outputs-action.php esp-outputs.php esp-style.css

4. PHP Script Update and Retrieve Output States

In this section, we're going to create a PHP script that is responsible for receiving incoming requests and interacting with your MySQL database. Edit the newly created file (esp-outputs-action.php) and copy the following snippet: <?php include_once('esp-database.php'); $action = $id = $name = $gpio = $state = ""; if ($_SERVER["REQUEST_METHOD"] == "POST") { $action = test_input($_POST["action"]); if ($action == "output_create") { $name = test_input($_POST["name"]); $board = test_input($_POST["board"]); $gpio = test_input($_POST["gpio"]); $state = test_input($_POST["state"]); $result = createOutput($name, $board, $gpio, $state); $result2 = getBoard($board); if(!$result2->fetch_assoc()) { createBoard($board); } echo $result; } else { echo "No data posted with HTTP POST."; } } if ($_SERVER["REQUEST_METHOD"] == "GET") { $action = test_input($_GET["action"]); if ($action == "outputs_state") { $board = test_input($_GET["board"]); $result = getAllOutputStates($board); if ($result) { while ($row = $result->fetch_assoc()) { $rows[$row["gpio"]] = $row["state"]; } } echo json_encode($rows); $result = getBoard($board); if($result->fetch_assoc()) { updateLastBoardTime($board); } } else if ($action == "output_update") { $id = test_input($_GET["id"]); $state = test_input($_GET["state"]); $result = updateOutput($id, $state); echo $result; } else if ($action == "output_delete") { $id = test_input($_GET["id"]); $board = getOutputBoardById($id); if ($row = $board->fetch_assoc()) { $board_id = $row["board"]; } $result = deleteOutput($id); $result2 = getAllOutputStates($board_id); if(!$result2->fetch_assoc()) { deleteBoard($board_id); } echo $result; } else { echo "Invalid HTTP request."; } } function test_input($data) { $data = trim($data); $data = stripslashes($data); $data = htmlspecialchars($data); return $data; } ?> View raw code

5. PHP Script for Database Functions

Edit your file esp-database.php that inserts, deletes, and retrieves data. Copy the next PHP script: <?php $servername = "localhost"; // Your Database name $dbname = "REPLACE_WITH_YOUR_DATABASE_NAME"; // Your Database user $username = "REPLACE_WITH_YOUR_USERNAME"; // Your Database user password $password = "REPLACE_WITH_YOUR_PASSWORD"; function createOutput($name, $board, $gpio, $state) { global $servername, $username, $password, $dbname; // Create connection $conn = new mysqli($servername, $username, $password, $dbname); // Check connection if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } $sql = "INSERT INTO Outputs (name, board, gpio, state) VALUES ('" . $name . "', '" . $board . "', '" . $gpio . "', '" . $state . "')"; if ($conn->query($sql) === TRUE) { return "New output created successfully"; } else { return "Error: " . $sql . "<br>" . $conn->error; } $conn->close(); } function deleteOutput($id) { global $servername, $username, $password, $dbname; // Create connection $conn = new mysqli($servername, $username, $password, $dbname); // Check connection if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } $sql = "DELETE FROM Outputs WHERE id='". $id . "'"; if ($conn->query($sql) === TRUE) { return "Output deleted successfully"; } else { return "Error: " . $sql . "<br>" . $conn->error; } $conn->close(); } function updateOutput($id, $state) { global $servername, $username, $password, $dbname; // Create connection $conn = new mysqli($servername, $username, $password, $dbname); // Check connection if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } $sql = "UPDATE Outputs SET state='" . $state . "' WHERE id='". $id . "'"; if ($conn->query($sql) === TRUE) { return "Output state updated successfully"; } else { return "Error: " . $sql . "<br>" . $conn->error; } $conn->close(); } function getAllOutputs() { global $servername, $username, $password, $dbname; // Create connection $conn = new mysqli($servername, $username, $password, $dbname); // Check connection if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } $sql = "SELECT id, name, board, gpio, state FROM Outputs ORDER BY board"; if ($result = $conn->query($sql)) { return $result; } else { return false; } $conn->close(); } function getAllOutputStates($board) { global $servername, $username, $password, $dbname; // Create connection $conn = new mysqli($servername, $username, $password, $dbname); // Check connection if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } $sql = "SELECT gpio, state FROM Outputs WHERE board='" . $board . "'"; if ($result = $conn->query($sql)) { return $result; } else { return false; } $conn->close(); } function getOutputBoardById($id) { global $servername, $username, $password, $dbname; // Create connection $conn = new mysqli($servername, $username, $password, $dbname); // Check connection if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } $sql = "SELECT board FROM Outputs WHERE id='" . $id . "'"; if ($result = $conn->query($sql)) { return $result; } else { return false; } $conn->close(); } function updateLastBoardTime($board) { global $servername, $username, $password, $dbname; // Create connection $conn = new mysqli($servername, $username, $password, $dbname); // Check connection if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } $sql = "UPDATE Boards SET last_request=now() WHERE board='". $board . "'"; if ($conn->query($sql) === TRUE) { return "Output state updated successfully"; } else { return "Error: " . $sql . "<br>" . $conn->error; } $conn->close(); } function getAllBoards() { global $servername, $username, $password, $dbname; // Create connection $conn = new mysqli($servername, $username, $password, $dbname); // Check connection if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } $sql = "SELECT board, last_request FROM Boards ORDER BY board"; if ($result = $conn->query($sql)) { return $result; } else { return false; } $conn->close(); } function getBoard($board) { global $servername, $username, $password, $dbname; // Create connection $conn = new mysqli($servername, $username, $password, $dbname); // Check connection if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } $sql = "SELECT board, last_request FROM Boards WHERE board='" . $board . "'"; if ($result = $conn->query($sql)) { return $result; } else { return false; } $conn->close(); } function createBoard($board) { global $servername, $username, $password, $dbname; // Create connection $conn = new mysqli($servername, $username, $password, $dbname); // Check connection if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } $sql = "INSERT INTO Boards (board) VALUES ('" . $board . "')"; if ($conn->query($sql) === TRUE) { return "New board created successfully"; } else { return "Error: " . $sql . "<br>" . $conn->error; } $conn->close(); } function deleteBoard($board) { global $servername, $username, $password, $dbname; // Create connection $conn = new mysqli($servername, $username, $password, $dbname); // Check connection if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } $sql = "DELETE FROM Boards WHERE board='". $board . "'"; if ($conn->query($sql) === TRUE) { return "Board deleted successfully"; } else { return "Error: " . $sql . "<br>" . $conn->error; } $conn->close(); } ?> View raw code Before saving the file, you need to modify the $dbname, $username and $password variables with your unique details: // Your Database name $dbname = "example_esp_data"; // Your Database user $username = "example_esp_board"; // Your Database user password $password = "YOUR_USER_PASSWORD"; After adding the database name, username and password, save the file and continue with this tutorial.

6. PHP Script Control Buttons

You'll also need to add a CSS file to style your dashboard (esp-style.css). Copy that CSS to your file and save it: /** Rui Santos Complete project details at https://RandomNerdTutorials.com/control-esp32-esp8266-gpios-from-anywhere/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. **/ html { font-family: Arial; display: inline-block; text-align: center; } h2 { font-size: 3.0rem; } body { max-width: 600px; margin:0px auto; padding-bottom: 25px; } .switch { position: relative; display: inline-block; width: 120px; height: 68px; } .switch input { display: none } .slider { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #949494; border-radius: 34px; } .slider:before { position: absolute; content: ""; height: 52px; width: 52px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 68px; } input:checked+.slider { background-color: #008B74; } input:checked+.slider:before { -webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px); } input[type=text], input[type=number], select { width: 100%; padding: 12px 20px; margin: 8px 0; display: inline-block; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } input[type=submit] { width: 100%; background-color: #008B74; color: white; padding: 14px 20px; margin: 8px 0; border: none; border-radius: 4px; cursor: pointer; } input[type=submit]:hover { background-color: #005a4c; } div { text-align: left; border-radius: 4px; background-color: #efefef; padding: 20px; } View raw code Finally, copy the next PHP script to your esp-outputs.php files that will display your control buttons and allow you to create/delete buttons: <!-- Rui Santos Complete project details at https://RandomNerdTutorials.com/control-esp32-esp8266-gpios-from-anywhere/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. --> <?php include_once('esp-database.php'); $result = getAllOutputs(); $html_buttons = null; if ($result) { while ($row = $result->fetch_assoc()) { if ($row["state"] == "1"){ $button_checked = "checked"; } else { $button_checked = ""; } $html_buttons .= '<h3>' . $row["name"] . ' - Board '. $row["board"] . ' - GPIO ' . $row["gpio"] . ' (<i><a onclick="deleteOutput(this)" href="javascript:void(0);"id"] . '">Delete</a></i>)</h3><label><input type="checkbox" onchange="updateOutput(this)"id"] . '" ' . $button_checked . '><span></span></label>'; } } $result2 = getAllBoards(); $html_boards = null; if ($result2) { $html_boards .= '<h3>Boards</h3>'; while ($row = $result2->fetch_assoc()) { $row_reading_time = $row["last_request"]; // Uncomment to set timezone to - 1 hour (you can change 1 to any number) //$row_reading_time = date("Y-m-d H:i:s", strtotime("$row_reading_time - 1 hours")); // Uncomment to set timezone to + 4 hours (you can change 4 to any number) //$row_reading_time = date("Y-m-d H:i:s", strtotime("$row_reading_time + 7 hours")); $html_boards .= '<p><strong>Board ' . $row["board"] . '</strong> - Last Request Time: '. $row_reading_time . '</p>'; } } ?> <!DOCTYPE HTML> <html> <head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" type="text/css" href="esp-style.css"> <title>ESP Output Control</title> </head> <body> <h2>ESP Output Control</h2> <?php echo $html_buttons; ?> <br><br> <?php echo $html_boards; ?> <br><br> <div><form onsubmit="return createOutput();"> <h3>Create New Output</h3> <label for="outputName">Name</label> <input type="text" name="name"><br> <label for="outputBoard">Board ID</label> <input type="number" name="board" min="0"> <label for="outputGpio">GPIO Number</label> <input type="number" name="gpio" min="0"> <label for="outputState">Initial GPIO State</label> <select name="state"> <option value="0">0 = OFF</option> <option value="1">1 = ON</option> </select> <input type="submit" value="Create Output"> <p><strong>Note:</strong> in some devices, you might need to refresh the page to see your newly created buttons or to remove deleted buttons.</p> </form></div> <script> function updateOutput(element) { var xhr = new XMLHttpRequest(); if(element.checked){ xhr.open("GET", "esp-outputs-action.php?action=output_update&id="+element.id+"&state=1", true); } else { xhr.open("GET", "esp-outputs-action.php?action=output_update&id="+element.id+"&state=0", true); } xhr.send(); } function deleteOutput(element) { var result = confirm("Want to delete this output?"); if (result) { var xhr = new XMLHttpRequest(); xhr.open("GET", "esp-outputs-action.php?action=output_delete&id="+element.id, true); xhr.send(); alert("Output deleted"); setTimeout(function(){ window.location.reload(); }); } } function createOutput(element) { var xhr = new XMLHttpRequest(); xhr.open("POST", "esp-outputs-action.php", true); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.onreadystatechange = function() { if (this.readyState === XMLHttpRequest.DONE && this.status === 200) { alert("Output created"); setTimeout(function(){ window.location.reload(); }); } } var outputName = document.getElementById("outputName").value; var outputBoard = document.getElementById("outputBoard").value; var outputGpio = document.getElementById("outputGpio").value; var outputState = document.getElementById("outputState").value; var httpRequestData = "action=output_create&name="+outputName+"&board="+outputBoard+"&gpio="+outputGpio+"&state="+outputState; xhr.send(httpRequestData); } </script> </body> </html> View raw code If you try to access your domain name in the following URL path, you'll see the following: https://example.com/esp-outputs.php That's it! You should see that web page with your default button. The default button is called Built-in LED, it's assigned to Board 1 and controls GPIO 2.

7. Setting Up the ESP32 or ESP8266

This project is compatible with both the ESP32 and ESP8266 boards. You just need to assemble a simple circuit and upload the sketches provided. ESP32 vs ESP8266 Development Boards

Parts Required

To test this project, we'll connect some LEDs to the ESP32 and ESP8266 GPIOs. Here's a list of parts you need to build the circuit for this project: ESP32 board (read Best ESP32 dev boards) ESP8266 board (read Best ESP8266 dev boards) 5x LEDs 5x 220 Ohm resistors Jumper wires Breadboard You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Schematics

For this example, we'll use an ESP32 board with 3 LEDs and an ESP8266 with 2 LEDs. Instead of LEDs, you can connect a relay module or any other device to the ESP GPIOs. Relay module with ESP32 Relay module with ESP8266

LEDs wiring to ESP32 Board #1

Recommended reading: which ESP32 GPIOs should you use.

LEDs wiring to ESP8266 Board #2

Recommended reading: which ESP8266 GPIOs should you use.

ESP32 Code Board #1

We'll program the ESP32/ESP8266 using Arduino IDE, so you must have the ESP add-on installed in your Arduino IDE. Follow one of the next tutorials depending on the board you're using: Install the ESP32 Board in Arduino IDE Install the ESP8266 Board in Arduino IDE You also need to install the Arduino_JSON library. You can install this library in the Arduino IDE Library Manager. Just go to Sketch > Include Library > Manage Libraries and search for the library name as follows: After installing the necessary board add-ons and libraries, copy the following code to your Arduino IDE, but don't upload it yet. You need to make some changes to make it work for you. /* Rui Santos Complete project details at https://RandomNerdTutorials.com/control-esp32-esp8266-gpios-from-anywhere/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ #include <WiFi.h> #include <HTTPClient.h> #include <WiFiClientSecure.h> #include <Arduino_JSON.h> const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; //Your IP address or domain name with URL path const char* serverName = "https://example.com/esp-outputs-action.php?action=outputs_state&board=1"; // Update interval time set to 5 seconds const long interval = 5000; unsigned long previousMillis = 0; String outputsState; void setup() { Serial.begin(115200); WiFi.begin(ssid, password); Serial.println("Connecting"); while(WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.print("Connected to WiFi network with IP Address: "); Serial.println(WiFi.localIP()); } void loop() { unsigned long currentMillis = millis(); if(currentMillis - previousMillis >= interval) { // Check WiFi connection status if(WiFi.status()== WL_CONNECTED ){ outputsState = httpGETRequest(serverName); Serial.println(outputsState); JSONVar myObject = JSON.parse(outputsState); // JSON.typeof(jsonVar) can be used to get the type of the var if (JSON.typeof(myObject) == "undefined") { Serial.println("Parsing input failed!"); return; } Serial.print("JSON object = "); Serial.println(myObject); // myObject.keys() can be used to get an array of all the keys in the object JSONVar keys = myObject.keys(); for (int i = 0; i < keys.length(); i++) { JSONVar value = myObject[keys[i]]; Serial.print("GPIO: "); Serial.print(keys[i]); Serial.print(" - SET to: "); Serial.println(value); pinMode(atoi(keys[i]), OUTPUT); digitalWrite(atoi(keys[i]), atoi(value)); } // save the last HTTP GET Request previousMillis = currentMillis; } else { Serial.println("WiFi Disconnected"); } } } String httpGETRequest(const char* serverName) { WiFiClientSecure *client = new WiFiClientSecure; // set secure client without certificate client->setInsecure(); HTTPClient https; // Your IP address with path or Domain name with URL path https.begin(*client, serverName); // Send HTTP POST request int httpResponseCode = https.GET(); String payload = "{}"; if (httpResponseCode>0) { Serial.print("HTTP Response code: "); Serial.println(httpResponseCode); payload = https.getString(); } else { Serial.print("Error code: "); Serial.println(httpResponseCode); } // Free resources https.end(); return payload; } View raw code Note: Most servers require you to make HTTPS requests. The code above makes HTTPS requests to be compliant with the requirements of most cloud servers nowadays. Your server doesn't support HTTPS? Use this code instead.

Setting your network credentials

You need to modify the following lines with your network credentials: SSID and password. The code is well commented on where you should make the changes. // Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Setting your serverName

You also need to type your domain name, so the ESP makes the HTTP GET request to your own server. const char* serverName = "https://example.com/esp-outputs-action.php?action=outputs_state&board=1"; Notice that on the URL serverName we have a parameter board=1. This indicates the board ID. If you want to add more boards, you should change that ID. That identifies the board you want to control. Now, you can upload the code to your board. It should work straight away. This project is already quite long, so we won't cover how the code works. In summary, your ESP32 makes an HTTP GET request to your server every X number of seconds to update the GPIOs states (by default it's set to 5 seconds). const long interval = 5000; Then, the board will update its outputs accordingly to the request response. Open your Serial Monitor and you should see something similar: The request retrieves a JSON object that contains the GPIO number and its state. In this case, it tells us that GPIO 2 should be LOW {2:0}.

ESP8266 Code Board #2

For this example, we're controlling the outputs from two boards simultaneously. You can use next code for your ESP8266 board: /* Rui Santos Complete project details at https://RandomNerdTutorials.com/control-esp32-esp8266-gpios-from-anywhere/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ #include <ESP8266WiFi.h> #include <ESP8266HTTPClient.h> #include <WiFiClientSecureBearSSL.h> #include <Arduino_JSON.h> const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; //Your IP address or domain name with URL path //const char* serverName = "https://example.com/esp-outputs-action.php?action=outputs_state&board=1"; // Update interval time set to 5 seconds const long interval = 5000; unsigned long previousMillis = 0; String outputsState; void setup() { Serial.begin(115200); WiFi.begin(ssid, password); Serial.println("Connecting"); while(WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.print("Connected to WiFi network with IP Address: "); Serial.println(WiFi.localIP()); } void loop() { unsigned long currentMillis = millis(); if(currentMillis - previousMillis >= interval) { // Check WiFi connection status if(WiFi.status()== WL_CONNECTED ){ outputsState = httpGETRequest(serverName); Serial.println(outputsState); JSONVar myObject = JSON.parse(outputsState); // JSON.typeof(jsonVar) can be used to get the type of the var if (JSON.typeof(myObject) == "undefined") { Serial.println("Parsing input failed!"); return; } Serial.print("JSON object = "); Serial.println(myObject); // myObject.keys() can be used to get an array of all the keys in the object JSONVar keys = myObject.keys(); for (int i = 0; i < keys.length(); i++) { JSONVar value = myObject[keys[i]]; Serial.print("GPIO: "); Serial.print(keys[i]); Serial.print(" - SET to: "); Serial.println(value); pinMode(atoi(keys[i]), OUTPUT); digitalWrite(atoi(keys[i]), atoi(value)); } // save the last HTTP GET Request previousMillis = currentMillis; } else { Serial.println("WiFi Disconnected"); } } } String httpGETRequest(const char* serverName) { std::unique_ptr<BearSSL::WiFiClientSecure>client(new BearSSL::WiFiClientSecure); // Ignore SSL certificate validation client->setInsecure(); HTTPClient https; // Your IP address with path or Domain name with URL path https.begin(*client, serverName); // Send HTTP POST request int httpResponseCode = https.GET(); String payload = "{}"; if (httpResponseCode>0) { Serial.print("HTTP Response code: "); Serial.println(httpResponseCode); payload = https.getString(); } else { Serial.print("Error code: "); Serial.println(httpResponseCode); } // Free resources https.end(); return payload; } View raw code To prepare the code for your ESP8266, just enter the SSID, password, domain name, and board ID (in this case, it's board ID number 2). Note: Most servers require you to make HTTPS requests. The code above makes HTTPS requests to be compliant with the requirements of most cloud servers nowadays. Your server doesn't support HTTPS? Use this code instead.

Demonstration

After completing all the steps, power both your ESP boards. If you open your domain name in this URL path: https://example.com/esp-outputs.php You should see the default button in your Dashboard: If you press that button on and off, you should be able to control GPIO 2 from your ESP32 Board #1. You can add more buttons to your project, type a name (LED 2), set board the id to number 1, then type the desired GPIO that you want to control (33). Create another button for Board 1 to control GPIO 32. Then, add two buttons for Board 2 (GPIO 2 and GPIO 4). At any point in time, you can use the delete link to remove buttons from your Dashboard or use the form at the bottom to create more. Note: in some devices, you might need to refresh the page to see your newly created buttons or to remove deleted buttons. Finally, there's a section that shows the last time a board made a request and updated its outputs. Since this is not a two-way communication, when you press the buttons to control your outputs, your board doesn't update the outputs instantly. It will take a few seconds for your ESP board to make a new HTTP GET request and update its output states. With the Last Request Time section, you can see when that happened. Just refresh the page to see the updated values. The web page is also mobile responsive, so you can use any device to access your server.

Wrapping Up

In this tutorial you've learned how to control your ESP32 and ESP8266 outputs from anywhere in the world. This requires that you have your own server and domain name (alternatively, you can use a Raspberry Pi LAMP Server for local access). There are many other features that you can add to your server, you can merge it with our previous projects to display sensor readings. Feel free to add more ESP boards to run simultaneously and define other outputs to control. I encourage you to change the web page appearance, add more features like email notifications, publish data from different sensors, use multiple ESP boards, and much more.

DIY Cloud Weather Station with ESP32/ESP8266 (MySQL Database and PHP)

Build a cloud weather station dashboard to visualize your ESP32 or ESP8266 sensor readings from anywhere in the world. You'll visualize your sensor data displayed on gauges and on a table. The ESP32 or ESP8266 will make an HTTP POST request to a PHP script to insert your data into a MySQL database. Updated on 27 March 2023 Previously, we've stored sensor readings in a database and displayed them on a table or charts that you can access from anywhere using your own server. Now, I've decided to take a few steps further and add some more information to the web page. I've added two gauges to display the latest temperature and humidity readings as well as some statistics about the minimum, maximum and average readings from a number of readings that you can define. You can also visualize all the latest readings on a table and you can select how many readings you want to show. To build this project, you'll use these technologies: ESP32 or ESP8266 programmed with Arduino IDE Hosting server and domain name PHP script to insert data into MySQL and display it on a web page MySQL database to store readings Table of Contents This project is divided into the following main sections:
    Hosting Your PHP Application and MySQL Database Preparing Your MySQL Database PHP Script HTTP POST Receive and Insert Data in MySQL Database PHP Script for Database Functions PHP Script Display Database Readings on Gauges and Table Setting Up the ESP32 or ESP8266

Watch the Video Demonstration

To see how the project works, you can watch the following video demonstration:

0. Download Source Code

For this project, you'll need these files: SQL query to create your table: SensorData_Table.sql Insert and access database readings: esp-database.php Handle HTTP Post requests: esp-post-data.php CSS file to style your web page: esp-style.css Display your sensor readings: esp-weather-station.php Arduino Sketch for ESP32: HTTPS_ESP32_Cloud_Weather_Station.ino Arduino Sketch for ESP8266 : HTTPS_ESP8266_Cloud_Weather_Station.ino If your server doesn't support HTTPS, use this Arduino Sketch (compatible with the ESP32 and ESP8266: ESP_HTTP_POST_MySQL.ino Download all projects files

1. Hosting Your PHP Application and MySQL Database

The goal of this project is to have your own domain name and hosting account that allows you to store sensor readings from the ESP32 or ESP8266. You can visualize the readings from anywhere in the world by accessing your own server domain. Here's a high-level overview of how the project works:
    You have an ESP32 or ESP8266 that sends sensor readings to your own server. For this, you have your board connected to your router; In your server, there's a PHP script that allows you to store your readings in a MySQL database; Then, another PHP script will display the web page with the gauges, table, and all the other information; Finally, you can visualize the readings from anywhere in the world by accessing your own domain name.

Hosting Services

I recommend using one of the following hosting services that can handle all the project requirements: Bluehost (user-friendly with cPanel): free domain name when you sign up for the 3-year plan. I recommend choosing the unlimited websites option; Digital Ocean: Linux server that you manage through a command line. I only recommended this option for advanced users. Those two services are the ones that I use and personally recommend, but you can use any other hosting service. Any hosting service that offers PHP and MySQL will work with this tutorial. If you don't have a hosting account, I recommend signing up for Bluehost. Get Hosting and Domain Name with Bluehost When buying a hosting account, you'll also have to purchase a domain name. This is what makes this project interesting: you'll be able to go to your domain name (https://example.com) and see your ESP readings. If you like our projects, you might consider signing up for one of the recommended hosting services, because you'll be supporting our work. Note: you can also run a LAMP (Linux, Apache, MySQL, PHP) server on a Raspberry Pi to access data in your local network. However, the purpose of this tutorial is to publish readings in your own domain name that you can access from anywhere in the world. This allows you to easily access your ESP readings without relying on a third-party IoT platform.

2. Preparing Your MySQL Database

After signing up for a hosting account and setting up a domain name, you can login to your cPanel or similar dashboard. After that, follow the next steps to create your database, username, password, and SQL table.

Creating a database and user

Open the Advanced tab: 1. Type database in the search bar and select MySQL Database Wizard. 2. Enter your desired Database name. In my case, the database name is esp_data. Then, press the Next Step button: Note: later you'll have to use the database name with the prefix that your host gives you (my database prefix in the screenshot above is blurred). I'll refer to it as example_esp_data from now on. 3. Type your Database username and set a password. You must save all those details because you'll need them later to establish a database connection with your PHP code. That's it! Your new database and user were created successfully. Now, save all your details because you'll need them later: Database name: example_esp_data Username: example_esp_board Password: your password

Creating a SQL table

After creating your database and user, go back to cPanel dashboard and search for phpMyAdmin. In the left sidebar, select your database name example_esp_data and open the SQL tab. Important: make sure you've opened the example_esp_data database. Then, click the SQL tab. If you don't follow these exact steps and run the SQL query, you might create a table in the wrong database. Copy the SQL query in the following snippet: CREATE TABLE SensorData ( id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY, sensor VARCHAR(30) NOT NULL, location VARCHAR(30) NOT NULL, value1 VARCHAR(10), value2 VARCHAR(10), value3 VARCHAR(10), reading_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) View raw code Paste it in the SQL query field (highlighted with a red rectangle) and press the Go button to create your table: After that, you should see your newly created table called SensorData in the example_esp_data database as shown in the figure below:

3. PHP Script HTTP POST Receive and Insert Data in MySQL Database

In this section, we're going to create a PHP script that is responsible for receiving incoming requests from the ESP32 or ESP8266 and inserting the data into a MySQL database. If you're using a hosting provider with cPanel, you can search for File Manager: ESP32 ESP8266 CPanel Open Edit PHP Files with File Manager Then, select the public_html option and press the + File button to create a new .php file. Note: if you're following this tutorial and you're not familiar with PHP or MySQL, I recommend creating these exact files. Otherwise, you'll need to modify the ESP sketch provided with different URL paths. Create a new file in /public_html with this exact name and extension: esp-post-data.php Edit the newly created file (esp-post-data.php) and copy the following snippet: <!-- Rui Santos Complete project details at https://RandomNerdTutorials.com/cloud-weather-station-esp32-esp8266/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. --> <?php include_once('esp-database.php'); // Keep this API Key value to be compatible with the ESP code provided in the project page. If you change this value, the ESP sketch needs to match $api_key_value = "tPmAT5Ab3j7F9"; $api_key= $sensor = $location = $value1 = $value2 = $value3 = ""; if ($_SERVER["REQUEST_METHOD"] == "POST") { $api_key = test_input($_POST["api_key"]); if($api_key == $api_key_value) { $sensor = test_input($_POST["sensor"]); $location = test_input($_POST["location"]); $value1 = test_input($_POST["value1"]); $value2 = test_input($_POST["value2"]); $value3 = test_input($_POST["value3"]); $result = insertReading($sensor, $location, $value1, $value2, $value3); echo $result; } else { echo "Wrong API Key provided."; } } else { echo "No data posted with HTTP POST."; } function test_input($data) { $data = trim($data); $data = stripslashes($data); $data = htmlspecialchars($data); return $data; } View raw code

4. PHP Script for Database Functions

Create a new file in /public_html that is responsible for inserting and accessing data in your database. Name your file: esp-database.php Copy that PHP script: <!-- Rui Santos Complete project details at https://RandomNerdTutorials.com/cloud-weather-station-esp32-esp8266/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. --> <?php $servername = "localhost"; // REPLACE with your Database name $dbname = "REPLACE_WITH_YOUR_DATABASE_NAME"; // REPLACE with Database user $username = "REPLACE_WITH_YOUR_USERNAME"; // REPLACE with Database user password $password = "REPLACE_WITH_YOUR_PASSWORD"; function insertReading($sensor, $location, $value1, $value2, $value3) { global $servername, $username, $password, $dbname; // Create connection $conn = new mysqli($servername, $username, $password, $dbname); // Check connection if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } $sql = "INSERT INTO SensorData (sensor, location, value1, value2, value3) VALUES ('" . $sensor . "', '" . $location . "', '" . $value1 . "', '" . $value2 . "', '" . $value3 . "')"; if ($conn->query($sql) === TRUE) { return "New record created successfully"; } else { return "Error: " . $sql . "<br>" . $conn->error; } $conn->close(); } function getAllReadings($limit) { global $servername, $username, $password, $dbname; // Create connection $conn = new mysqli($servername, $username, $password, $dbname); // Check connection if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } $sql = "SELECT id, sensor, location, value1, value2, value3, reading_time FROM SensorData order by reading_time desc limit " . $limit; if ($result = $conn->query($sql)) { return $result; } else { return false; } $conn->close(); } function getLastReadings() { global $servername, $username, $password, $dbname; // Create connection $conn = new mysqli($servername, $username, $password, $dbname); // Check connection if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } $sql = "SELECT id, sensor, location, value1, value2, value3, reading_time FROM SensorData order by reading_time desc limit 1" ; if ($result = $conn->query($sql)) { return $result->fetch_assoc(); } else { return false; } $conn->close(); } function minReading($limit, $value) { global $servername, $username, $password, $dbname; // Create connection $conn = new mysqli($servername, $username, $password, $dbname); // Check connection if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } $sql = "SELECT MIN(" . $value . ") AS min_amount FROM (SELECT " . $value . " FROM SensorData order by reading_time desc limit " . $limit . ") AS min"; if ($result = $conn->query($sql)) { return $result->fetch_assoc(); } else { return false; } $conn->close(); } function maxReading($limit, $value) { global $servername, $username, $password, $dbname; // Create connection $conn = new mysqli($servername, $username, $password, $dbname); // Check connection if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } $sql = "SELECT MAX(" . $value . ") AS max_amount FROM (SELECT " . $value . " FROM SensorData order by reading_time desc limit " . $limit . ") AS max"; if ($result = $conn->query($sql)) { return $result->fetch_assoc(); } else { return false; } $conn->close(); } function avgReading($limit, $value) { global $servername, $username, $password, $dbname; // Create connection $conn = new mysqli($servername, $username, $password, $dbname); // Check connection if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } $sql = "SELECT AVG(" . $value . ") AS avg_amount FROM (SELECT " . $value . " FROM SensorData order by reading_time desc limit " . $limit . ") AS avg"; if ($result = $conn->query($sql)) { return $result->fetch_assoc(); } else { return false; } $conn->close(); } ?> View raw code Before saving the file, you need to modify the $dbname, $username and $password variables with your unique details: // Your Database name $dbname = "example_esp_data"; // Your Database user $username = "example_esp_board"; // Your Database user password $password = "YOUR_USER_PASSWORD"; After adding the database name, username and password, save the file and continue with this tutorial. If you try to access your domain name in the next URL path, you'll see the following: https://example.com/esp-post-data.php

5. PHP Script Display Database Readings on Gauges and Table

You'll also need to add a CSS file to style your dashboard, name it: esp-style.css: Copy that CSS to your file and save it: /** Rui Santos Complete project details at https://RandomNerdTutorials.com/cloud-weather-station-esp32-esp8266/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. **/ body { width: 60%; margin: auto; text-align: center; font-family: Arial; top: 50%; left: 50%; } @media screen and (max-width: 800px) { body { width: 100%; } } table { margin-left: auto; margin-right: auto; } div { margin-left: auto; margin-right: auto; } h2 { font-size: 2.5rem; } .header { padding: 1rem; margin: 0 0 2rem 0; background: #f2f2f2; } h1 { font-size: 2rem; font-family: Arial, sans-serif; text-align: center; text-transform: uppercase; } .content { display: flex; } @media screen and (max-width: 500px) /* Mobile */ { .content { flex-direction: column; } } .mask { position: relative; overflow: hidden; display: block; width: 12.5rem; height: 6.25rem; margin: 1.25rem; } .semi-circle { position: relative; display: block; width: 12.5rem; height: 6.25rem; background: linear-gradient(to right, #3498db 0%, #05b027 33%, #f1c40f 70%, #c0392b 100%); border-radius: 50% 50% 50% 50% / 100% 100% 0% 0%; } .semi-circle::before { content: ""; position: absolute; bottom: 0; left: 50%; z-index: 2; display: block; width: 8.75rem; height: 4.375rem; margin-left: -4.375rem; background: #fff; border-radius: 50% 50% 50% 50% / 100% 100% 0% 0%; } .semi-circle--mask { position: absolute; top: 0; left: 0; width: 12.5rem; height: 12.5rem; background: transparent; transform: rotate(120deg) translate3d(0, 0, 0); transform-origin: center center; backface-visibility: hidden; transition: all 0.3s ease-in-out; } .semi-circle--mask::before { content: ""; position: absolute; top: 0; left: 0%; z-index: 2; display: block; width: 12.625rem; height: 6.375rem; margin: -1px 0 0 -1px; background: #f2f2f2; border-radius: 50% 50% 50% 50% / 100% 100% 0% 0%; } .gauge--2 .semi-circle { background: #3498db; } .gauge--2 .semi-circle--mask { transform: rotate(20deg) translate3d(0, 0, 0); } #tableReadings { border-collapse: collapse; } #tableReadings td, #tableReadings th { border: 1px solid #ddd; padding: 10px; } #tableReadings tr:nth-child(even){ background-color: #f2f2f2; } #tableReadings tr:hover { background-color: #ddd; } #tableReadings th { padding: 10px; background-color: #2f4468; color: white; } View raw code Finally, create another PHP file in the /public_html directory that will display all the database content on a web page. Name your new file: esp-weather-station.php Edit the newly created file (esp-weather-station.php) and copy the following code: <!-- Rui Santos Complete project details at https://RandomNerdTutorials.com/cloud-weather-station-esp32-esp8266/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. --> <?php include_once('esp-database.php'); if (isset($_GET["readingsCount"])){ $data = $_GET["readingsCount"]; $data = trim($data); $data = stripslashes($data); $data = htmlspecialchars($data); $readings_count = $_GET["readingsCount"]; } // default readings count set to 20 else { $readings_count = 20; } $last_reading = getLastReadings(); $last_reading_temp = $last_reading["value1"]; $last_reading_humi = $last_reading["value2"]; $last_reading_time = $last_reading["reading_time"]; // Uncomment to set timezone to - 1 hour (you can change 1 to any number) //$last_reading_time = date("Y-m-d H:i:s", strtotime("$last_reading_time - 1 hours")); // Uncomment to set timezone to + 7 hours (you can change 7 to any number) //$last_reading_time = date("Y-m-d H:i:s", strtotime("$last_reading_time + 7 hours")); $min_temp = minReading($readings_count, 'value1'); $max_temp = maxReading($readings_count, 'value1'); $avg_temp = avgReading($readings_count, 'value1'); $min_humi = minReading($readings_count, 'value2'); $max_humi = maxReading($readings_count, 'value2'); $avg_humi = avgReading($readings_count, 'value2'); ?> <!DOCTYPE html> <html> <head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <link rel="stylesheet" type="text/css" href="esp-style.css"> <meta name="viewport" content="width=device-width, initial-scale=1"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> </head> <header> <h1> ESP Weather Station</h2> <form method="get"> <input type="number" name="readingsCount" min="1" placeholder="Number of readings (<?php echo $readings_count; ?>)"> <input type="submit" value="UPDATE"> </form> </header> <body> <p>Last reading: <?php echo $last_reading_time; ?></p> <section> <div> <h3>TEMPERATURE</h3> <div> <div></div> <div></div> </div> <p style="font-size: 30px;">--</p> <table cellspacing="5" cellpadding="5"> <tr> <th colspan="3">Temperature <?php echo $readings_count; ?> readings</th> </tr> <tr> <td>Min</td> <td>Max</td> <td>Average</td> </tr> <tr> <td><?php echo $min_temp['min_amount']; ?> °C</td> <td><?php echo $max_temp['max_amount']; ?> °C</td> <td><?php echo round($avg_temp['avg_amount'], 2); ?> °C</td> </tr> </table> </div> <div> <h3>HUMIDITY</h3> <div> <div></div> <div></div> </div> <p style="font-size: 30px;">--</p> <table cellspacing="5" cellpadding="5"> <tr> <th colspan="3">Humidity <?php echo $readings_count; ?> readings</th> </tr> <tr> <td>Min</td> <td>Max</td> <td>Average</td> </tr> <tr> <td><?php echo $min_humi['min_amount']; ?> %</td> <td><?php echo $max_humi['max_amount']; ?> %</td> <td><?php echo round($avg_humi['avg_amount'], 2); ?> %</td> </tr> </table> </div> </section> <?php echo '<h2> View Latest ' . $readings_count . ' Readings</h2> <table cellspacing="5" cellpadding="5"> <tr> <th>ID</th> <th>Sensor</th> <th>Location</th> <th>Value 1</th> <th>Value 2</th> <th>Value 3</th> <th>Timestamp</th> </tr>'; $result = getAllReadings($readings_count); if ($result) { while ($row = $result->fetch_assoc()) { $row_id = $row["id"]; $row_sensor = $row["sensor"]; $row_location = $row["location"]; $row_value1 = $row["value1"]; $row_value2 = $row["value2"]; $row_value3 = $row["value3"]; $row_reading_time = $row["reading_time"]; // Uncomment to set timezone to - 1 hour (you can change 1 to any number) //$row_reading_time = date("Y-m-d H:i:s", strtotime("$row_reading_time - 1 hours")); // Uncomment to set timezone to + 7 hours (you can change 7 to any number) //$row_reading_time = date("Y-m-d H:i:s", strtotime("$row_reading_time + 7 hours")); echo '<tr> <td>' . $row_id . '</td> <td>' . $row_sensor . '</td> <td>' . $row_location . '</td> <td>' . $row_value1 . '</td> <td>' . $row_value2 . '</td> <td>' . $row_value3 . '</td> <td>' . $row_reading_time . '</td> </tr>'; } echo '</table>'; $result->free(); } ?> <script> var value1 = <?php echo $last_reading_temp; ?>; var value2 = <?php echo $last_reading_humi; ?>; setTemperature(value1); setHumidity(value2); function setTemperature(curVal){ //set range for Temperature in Celsius -5 Celsius to 38 Celsius var minTemp = -5.0; var maxTemp = 38.0; //set range for Temperature in Fahrenheit 23 Fahrenheit to 100 Fahrenheit //var minTemp = 23; //var maxTemp = 100; var newVal = scaleValue(curVal, [minTemp, maxTemp], [0, 180]); $('.gauge--1 .semi-circle--mask').attr({ style: '-webkit-transform: rotate(' + newVal + 'deg);' + '-moz-transform: rotate(' + newVal + 'deg);' + 'transform: rotate(' + newVal + 'deg);' }); $("#temp").text(curVal + ' oC'); } function setHumidity(curVal){ //set range for Humidity percentage 0 % to 100 % var minHumi = 0; var maxHumi = 100; var newVal = scaleValue(curVal, [minHumi, maxHumi], [0, 180]); $('.gauge--2 .semi-circle--mask').attr({ style: '-webkit-transform: rotate(' + newVal + 'deg);' + '-moz-transform: rotate(' + newVal + 'deg);' + 'transform: rotate(' + newVal + 'deg);' }); $("#humi").text(curVal + ' %'); } function scaleValue(value, from, to) { var scale = (to[1] - to[0]) / (from[1] - from[0]); var capped = Math.min(from[1], Math.max(from[0], value)) - from[0]; return ~~(capped * scale + to[0]); } </script> </body> </html> View raw code If you try to access your domain name in the following URL path, you'll see the following: https://example.com/esp-weather-station.php That's it! If you see that web page with empty values in your browser, it means that everything is ready. In the next section, you'll learn how to insert data from your ESP32 or ESP8266 into the database.

6. Setting Up the ESP32 or ESP8266

This project is compatible with both the ESP32 and ESP8266 boards. You just need to assemble a simple circuit and upload the sketch provided to insert temperature, humidity, pressure, and more into your database every 10 minutes. The sketch is slightly different for each board. ESP32 vs ESP8266 Development Boards

Parts Required

For this example, we'll get sensor readings from the BME280 sensor. Here's a list of parts you need to build the circuit for this project: ESP32 board (read Best ESP32 dev boards) Alternative ESP8266 board (read Best ESP8266 dev boards) BME280 sensor Jumper wires Breadboard You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Schematics

The BME280 sensor module we're using communicates via I2C communication protocol, so you need to connect it to the ESP32 or ESP8266 I2C pins.

BME280 wiring to ESP32

The ESP32 I2C pins are: GPIO 22: SCL (SCK) GPIO 21: SDA (SDI) So, assemble your circuit as shown in the next schematic diagram (Guide for ESP32 with BME280 and ESP32 BME280 Web Server). Recommended reading: ESP32 Pinout Reference Guide

BME280 wiring to ESP8266

The ESP8266 I2C pins are: GPIO 5 (D1): SCL (SCK) GPIO 4 (D2): SDA (SDI) Assemble your circuit as in the next schematic diagram if you're using an ESP8266 board (read Guide for ESP8266 with BME280). Recommended reading: ESP8266 Pinout Reference Guide

Installing Libraries

We'll program the ESP32/ESP8266 using Arduino IDE, so you must have the ESP add-on installed in your Arduino IDE. Follow one of the next tutorials depending on the board you're using: Install the ESP32 Board in Arduino IDE you also need to install the BME280 Library and Adafruit_Sensor library Install the ESP8266 Board in Arduino IDE you also need to install the BME280 Library and Adafruit_Sensor library

ESP32 Code

Follow this section if you're using an ESP32. For an ESP8266 click here. After installing the necessary board add-ons, copy the following code to your Arduino IDE, but don't upload it yet. You need to make some changes to make it work for you. /* Rui Santos Complete project details at https://RandomNerdTutorials.com/esp32-esp8266-mysql-database-php/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ #include <WiFi.h> #include <WiFiClientSecure.h> #include <HTTPClient.h> #include <Wire.h> #include <Adafruit_Sensor.h> #include <Adafruit_BME280.h> // Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; // REPLACE with your Domain name and URL path or IP address with path const char* serverName = "https://example.com/esp-post-data.php"; // Keep this API Key value to be compatible with the PHP code provided in the project page. // If you change the apiKeyValue value, the PHP file /post-esp-data.php also needs to have the same key String apiKeyValue = "tPmAT5Ab3j7F9"; String sensorName = "BME280"; String sensorLocation = "Office"; /*#include <SPI.h> #define BME_SCK 18 #define BME_MISO 19 #define BME_MOSI 23 #define BME_CS 5*/ #define SEALEVELPRESSURE_HPA (1013.25) Adafruit_BME280 bme; // I2C //Adafruit_BME280 bme(BME_CS); // hardware SPI //Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // software SPI void setup() { Serial.begin(115200); WiFi.begin(ssid, password); Serial.println("Connecting"); while(WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.print("Connected to WiFi network with IP Address: "); Serial.println(WiFi.localIP()); // (you can also pass in a Wire library object like &Wire2) bool status = bme.begin(0x76); if (!status) { Serial.println("Could not find a valid BME280 sensor, check wiring or change I2C address!"); while (1); } } void loop() { //Check WiFi connection status if(WiFi.status()== WL_CONNECTED){ WiFiClientSecure *client = new WiFiClientSecure; client->setInsecure(); //don't use SSL certificate HTTPClient https; // Your Domain name with URL path or IP address with path https.begin(*client, serverName); // Specify content-type header https.addHeader("Content-Type", "application/x-www-form-urlencoded"); // Prepare your HTTP POST request data String httpRequestData = "api_key=" + apiKeyValue + "&sensor=" + sensorName + "&location=" + sensorLocation + "&value1=" + String(bme.readTemperature()) + "&value2=" + String(bme.readHumidity()) + "&value3=" + String(bme.readPressure()/100.0F) + ""; Serial.print("httpRequestData: "); Serial.println(httpRequestData); // You can comment the httpRequestData variable above // then, use the httpRequestData variable below (for testing purposes without the BME280 sensor) //String httpRequestData = "api_key=tPmAT5Ab3j7F9&sensor=BME280&location=Office&value1=24.75&value2=49.54&value3=1005.14"; // Send HTTP POST request int httpResponseCode = https.POST(httpRequestData); // If you need an HTTP request with a content type: text/plain //https.addHeader("Content-Type", "text/plain"); //int httpResponseCode = https.POST("Hello, World!"); // If you need an HTTP request with a content type: application/json, use the following: //https.addHeader("Content-Type", "application/json"); //int httpResponseCode = https.POST("{\"value1\":\"19\",\"value2\":\"67\",\"value3\":\"78\"}"); if (httpResponseCode>0) { Serial.print("HTTP Response code: "); Serial.println(httpResponseCode); } else { Serial.print("Error code: "); Serial.println(httpResponseCode); } // Free resources https.end(); } else { Serial.println("WiFi Disconnected"); } //Send an HTTP POST request every 30 seconds delay(30000); } View raw code

Setting your network credentials

You need to modify the following lines with your network credentials: SSID and password. The code is well commented on where you should make the changes. // Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Setting your serverName

You also need to type your domain name, so the ESP publishes the readings to your own server. const char* serverName = "https://example.com/esp-post-data.php"; Now, you can upload the code to your board. Note: Most servers require you to make HTTPS requests. The code above makes HTTPS requests to be compliant with the requirements of most cloud servers nowadays. Your server doesn't support HTTPS? Use this code instead.

How the code works

This project is already quite long, so we won't cover in detail how the code works, but here's a quick summary: Import all the libraries to make it work; Set variables that you might want to change (apiKeyValue, sensorName, sensorLocation); The apiKeyValue is just a random string that you can modify. It's used for security reasons, so only anyone that knows your API key can publish data to your database; Initialize the serial communication for debugging purposes; Establish a Wi-Fi connection with your router; Initialize the BME280 to get readings; Initialize a secure WiFi client. Then, in the loop() is where you actually make the HTTP POST request every 10 minutes with the latest BME280 readings: // Your Domain name with URL path or IP address with path http.begin(serverName); // Specify content-type header http.addHeader("Content-Type", "application/x-www-form-urlencoded"); // Prepare your HTTP POST request data String httpRequestData = "api_key=" + apiKeyValue + "&sensor=" + sensorName + "&location=" + sensorLocation + "&value1=" + String(bme.readTemperature()) + "&value2=" + String(bme.readHumidity()) + "&value3=" + String(bme.readPressure()/100.0F) + ""; int httpResponseCode = http.POST(httpRequestData); You can comment the httpRequestData variable above that concatenates all the BME280 readings and use the httpRequestData variable below for testing purposes: String httpRequestData = "api_key=tPmAT5Ab3j7F9&sensor=BME280&location=Office&value1=24.75&value2=49.54&value3=1005.14"; Learn more about HTTPS Requests with the ESP32: ESP32 HTTPS Requests (Arduino IDE).

ESP8266 Code

Follow this section if you're using an ESP8266. For an ESP32 check the section above. After installing the necessary board add-ons and libraries, copy the following code to your Arduino IDE, but don't upload it yet. You need to make some changes to make it work for you. /* Rui Santos Complete project details at https://RandomNerdTutorials.com/esp32-esp8266-mysql-database-php/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ #include <ESP8266WiFi.h> #include <ESP8266HTTPClient.h> #include <WiFiClientSecureBearSSL.h> #include <Wire.h> #include <Adafruit_Sensor.h> #include <Adafruit_BME280.h> // Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; // REPLACE with your Domain name and URL path or IP address with path const char* serverName = "https://example.com/esp-post-data.php"; // Keep this API Key value to be compatible with the PHP code provided in the project page. // If you change the apiKeyValue value, the PHP file /post-esp-data.php also needs to have the same key String apiKeyValue = "tPmAT5Ab3j7F9"; String sensorName = "BME280"; String sensorLocation = "Office"; /*#include <SPI.h> #define BME_SCK 18 #define BME_MISO 19 #define BME_MOSI 23 #define BME_CS 5*/ #define SEALEVELPRESSURE_HPA (1013.25) Adafruit_BME280 bme; // I2C //Adafruit_BME280 bme(BME_CS); // hardware SPI //Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // software SPI void setup() { Serial.begin(115200); WiFi.begin(ssid, password); Serial.println("Connecting"); while(WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.print("Connected to WiFi network with IP Address: "); Serial.println(WiFi.localIP()); // (you can also pass in a Wire library object like &Wire2) bool status = bme.begin(0x76); if (!status) { Serial.println("Could not find a valid BME280 sensor, check wiring or change I2C address!"); while (1); } } void loop() { //Check WiFi connection status if(WiFi.status()== WL_CONNECTED){ std::unique_ptr<BearSSL::WiFiClientSecure>client(new BearSSL::WiFiClientSecure); // Ignore SSL certificate validation client->setInsecure(); //create an HTTPClient instance HTTPClient https; // Your Domain name with URL path or IP address with path https.begin(*client, serverName); // Specify content-type header https.addHeader("Content-Type", "application/x-www-form-urlencoded"); // Prepare your HTTP POST request data String httpRequestData = "api_key=" + apiKeyValue + "&sensor=" + sensorName + "&location=" + sensorLocation + "&value1=" + String(bme.readTemperature()) + "&value2=" + String(bme.readHumidity()) + "&value3=" + String(bme.readPressure()/100.0F) + ""; Serial.print("httpRequestData: "); Serial.println(httpRequestData); // You can comment the httpRequestData variable above // then, use the httpRequestData variable below (for testing purposes without the BME280 sensor) //String httpRequestData = "api_key=tPmAT5Ab3j7F9&sensor=BME280&location=Office&value1=24.75&value2=49.54&value3=1005.14"; // Send HTTP POST request int httpResponseCode = https.POST(httpRequestData); // If you need an HTTP request with a content type: text/plain //http.addHeader("Content-Type", "text/plain"); //int httpResponseCode = https.POST("Hello, World!"); // If you need an HTTP request with a content type: application/json, use the following: //http.addHeader("Content-Type", "application/json"); //int httpResponseCode = https.POST("{\"value1\":\"19\",\"value2\":\"67\",\"value3\":\"78\"}"); if (httpResponseCode>0) { Serial.print("HTTP Response code: "); Serial.println(httpResponseCode); } else { Serial.print("Error code: "); Serial.println(httpResponseCode); } // Free resources https.end(); } else { Serial.println("WiFi Disconnected"); } //Send an HTTP POST request every 30 seconds delay(30000); } View raw code

Setting your network credentials

You need to modify the following lines with your network credentials: SSID and password. The code is well commented on where you should make the changes. // Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Setting your serverName

You also need to type your domain name, so the ESP publishes the readings to your own server. const char* serverName = "https://example.com/esp-post-data.php"; Now, you can upload the code to your board. Note: Most servers require you to make HTTPS requests. The code above makes HTTPS requests to be compliant with the requirements of most cloud servers nowadays. Your server doesn't support HTTPS? Use this code instead. Learn more about HTTPS Requests with the ESP8266: ESP8266 NodeMCU HTTPS Requests (Arduino IDE).

Demonstration

After completing all the steps, let your ESP board collect some readings and publish them to your server. If everything is correct, this is what you should see in your Arduino IDE Serial Monitor: If you open your domain name in this URL path: https://example.com/esp-weather-station.php You should see the latest 20 readings stored in your database. There are two gauges that show the latest temperature and humidity readings, and a timestamp. Refresh the web page to see the latest readings: There's a field where you can type the number of readings to visualize, as well as the number of readings for these statistics: minimum, maximum, and average. By default, it's set to 20. For example, if you type 30 and press the update button, you'll see that your web page updates and recalculates all the values. The web page is also mobile responsive, so you can use any device to access it: You can also go to phpMyAdmin to manage the data stored in your SensorData table. You can delete it, edit, etc

Wrapping Up

In this tutorial you've learned how to publish sensor data into a database in your own server domain that you can access from anywhere in the world. This requires that you have your own server and domain name (alternatively, you can use a Raspberry Pi LAMP Server for local access). I encourage you to change the web page appearance, add more features (like email notifications), publish data from different sensors, use multiple ESP boards, and much more.

Send Email Notification using PHP Script

In this project, you'll build an ESP32 or ESP8266 client that makes an HTTP POST request to a PHP script to send an email notification with sensor readings. Updated on 27 March 2023 You can also set a threshold value, so your email notification is only sent if the temperature/humidity/pressure is above a certain value. As an example, we'll be using a BME280 sensor connected to an ESP32 or ESP8266 board. You can modify the code provided to send readings from a different sensor or even use multiple boards. This is a great way to send email notifications using the ESP32 or ESP8266 without relying on IFTTT or an SMTP server. In order to build this project, you will:
    Host a server and setup a domain name Create PHP script to send email notifications Program ESP32 or ESP8266 with Arduino IDE
This project is also a great addition to build upon our previous projects: ESP32/ESP8266 Insert Data into MySQL Database using PHP and Arduino IDE Visualize Your Sensor Readings from Anywhere in the World

1. Hosting Your PHP Application

The goal of this project is to have your own domain name and hosting account that allows you to send email notifications (without using an SMTP server, IFTTT, etc). Here's a high-level overview of how the project works: I recommend using one of the following hosting services that can handle all the project requirements: Bluehost (user-friendly with cPanel): free domain name when you sign up for the 3-year plan. I recommend choosing the unlimited websites option; Digital Ocean: Linux server that you manage through a command line. I only recommended this option for advanced users. Those two services are the ones that I use and personally recommend, but you can use any other hosting service. Any hosting service that offers PHP and MySQL will work with this tutorial. If you don't have a hosting account, I recommend signing up for Bluehost. Get Hosting and Domain Name with Bluehost When buying a hosting account, you'll also have to purchase a domain name. If you like our projects, you might consider signing up for one of the recommended hosting services, because you'll be supporting our work. Note: you can also run a LAMP (Linux, Apache, MySQL, PHP) server on a Raspberry Pi, but it can't send emails standalone. To send an email with a Raspberry Pi using PHP, you need to use an SMTP (Simple Mail Transfer Protocol) server.

2. PHP Script HTTP Send Email Notification

After signing up for a hosting account and setting up a domain name, you can login to your cPanel or similar dashboard. After that, follow the next steps to create a PHP script that is responsible for receiving incoming requests from the ESP32/ESP8266 and sending an email. If you're using a hosting provider with cPanel, go to Advanced and search for File Manager: Then, select the public_html option and press the + File button to create a new .php file. Note: if you're following this tutorial and you're not familiar with PHP, I recommend creating that exact file. Otherwise, you'll need to modify the ESP sketch provided with a different URL path. Create a new file in /public_html with this exact name and extension: email-notification.php Edit the newly created file (email-notification.php) and copy the following script: <?php /* Rui Santos Complete project details at https://RandomNerdTutorials.com/esp32-esp8266-send-email-notification/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ // Receiver Email Address (where to send email notification) $email_address = "[email protected]"; // Keep this API Key value to be compatible with the ESP code provided in the project page. If you change this value, the ESP sketch needs to match $api_key_value = "tPmAT5Ab3j7F9"; $api_key = $value1 = $value2 = $value3 = ""; if ($_SERVER["REQUEST_METHOD"] == "POST") { $api_key = test_input($_POST["api_key"]); if($api_key == $api_key_value) { $value1 = test_input($_POST["value1"]); $value2 = test_input($_POST["value2"]); $value3 = test_input($_POST["value3"]); // Email message $email_msg = "Temperature: " . $value1 . "oC\nHumidity: " . $value2 . "%\nPressure: " . $value3 . "hPa"; // Use wordwrap() if lines are longer than 70 characters $email_msg = wordwrap($email_msg, 70); // Uncomment the next if statement to set a threshold // ($value1 = temperature, $value2 = humidity, $value3 = pressure) /*if($value1 < 24.0){ echo "Temperature below threshold, don't send email"; exit; }*/ // send email with mail(receiver email address, email subject, email message) mail($email_address, "[NEW] ESP BME280 Readings", $email_msg); echo "Email sent"; } else { echo "Wrong API Key provided."; } } else { echo "No data posted with HTTP POST."; } function test_input($data) { $data = trim($data); $data = stripslashes($data); $data = htmlspecialchars($data); return $data; } View raw code Before saving the file, you need to modify the $email_address variable with the receiver email address: // Receiver Email Address (where to send email notification) $email_address = "[email protected]"; After adding the receiver email, save the file and continue with this tutorial. If you try to access your domain name in the next URL path, you'll see the following: https://example.com/email-notification.php If you see that message, it means that everything is being setup properly. You can continue with this project.

3. Setting Up Your ESP32 or ESP8266

This project is compatible with both the ESP32 and ESP8266 boards. You just need to assemble a simple circuit and upload the sketch provided to publish temperature, humidity, and pressure readings to your PHP script, which will then be responsible to handle email notifications. The sketch is slightly different for each board.

Parts Required

For this example, we'll get sensor readings from the BME280 sensor. Here's a list of parts you need to build the circuit for this project: ESP32 board (read Best ESP32 dev boards) Alternative ESP8266 board (read Best ESP8266 dev boards) BME280 sensor Jumper wires Breadboard You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Schematics

The BME280 sensor module we're using communicates via I2C communication protocol, so you need to connect it to the ESP32 or ESP8266 I2C pins.

BME280 wiring to ESP32

BME280 ESP32
SCK (SCL Pin) GPIO 22
SDI (SDA pin) GPIO 21
So, assemble your circuit as shown in the next schematic diagram (read the complete Guide for ESP32 with BME280). Recommended reading: ESP32 Pinout Reference Guide

BME280 wiring to ESP8266

BME280 ESP8266
SCK (SCL Pin) GPIO 5
SDI (SDA pin) GPIO 4
Assemble your circuit as in the next schematic diagram if you're using an ESP8266 board (read the complete Guide for ESP8266 with BME280). Recommended reading: ESP8266 Pinout Reference Guide

Installing Libraries

We'll program the ESP32/ESP8266 using Arduino IDE, so you must have the ESP32/ESP8266 add-on installed in your Arduino IDE. Follow one of the next tutorials depending on the board you're using: Install the ESP32 Board in Arduino IDE you also need to install the BME280 Library and Adafruit_Sensor library Install the ESP8266 Board in Arduino IDE you also need to install the BME280 Library and Adafruit_Sensor library

ESP32 Code

Follow this section if you're using an ESP32. For an ESP8266 click here. After installing the necessary board add-ons, copy the following code to your Arduino IDE, but don't upload it yet. You need to make some changes to make it work for you. /* Rui Santos Complete project details at https://RandomNerdTutorials.com/esp32-esp8266-send-email-notification/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ #include <WiFi.h> #include <WiFiClientSecure.h> #include <HTTPClient.h> #include <Wire.h> #include <Adafruit_Sensor.h> #include <Adafruit_BME280.h> // Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; // REPLACE with your Domain name and URL path or IP address with path const char* serverName = "https://example.com/email-notification.php"; // Keep this API Key value to be compatible with the PHP code provided in the project page. // If you change the apiKeyValue value, the PHP file /email-notification.php also needs to have the same key String apiKeyValue = "tPmAT5Ab3j7F9"; /*#include <SPI.h> #define BME_SCK 18 #define BME_MISO 19 #define BME_MOSI 23 #define BME_CS 5*/ #define SEALEVELPRESSURE_HPA (1013.25) Adafruit_BME280 bme; // I2C //Adafruit_BME280 bme(BME_CS); // hardware SPI //Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // software SPI void setup() { Serial.begin(115200); WiFi.begin(ssid, password); Serial.println("Connecting"); while(WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.print("Connected to WiFi network with IP Address: "); Serial.println(WiFi.localIP()); // (you can also pass in a Wire library object like &Wire2) bool status = bme.begin(0x76); if (!status) { Serial.println("Could not find a valid BME280 sensor, check wiring or change I2C address!"); while (1); } //Check WiFi connection status if(WiFi.status()== WL_CONNECTED){ WiFiClientSecure *client = new WiFiClientSecure; client->setInsecure(); //don't use SSL certificate HTTPClient https; // Your Domain name with URL path or IP address with path https.begin(*client, serverName); // Specify content-type header https.addHeader("Content-Type", "application/x-www-form-urlencoded"); // Prepare your HTTP POST request data String httpRequestData = "api_key=" + apiKeyValue + "&value1=" + String(bme.readTemperature()) + "&value2=" + String(bme.readHumidity()) + "&value3=" + String(bme.readPressure()/100.0F) + ""; Serial.print("httpRequestData: "); Serial.println(httpRequestData); // You can comment the httpRequestData variable above // then, use the httpRequestData variable below (for testing purposes without the BME280 sensor) //String httpRequestData = "api_key=tPmAT5Ab3j7F9&sensor=BME280&location=Office&value1=24.75&value2=49.54&value3=1005.14"; // Send HTTP POST request int httpResponseCode = https.POST(httpRequestData); // If you need an HTTP request with a content type: text/plain //https.addHeader("Content-Type", "text/plain"); //int httpResponseCode = https.POST("Hello, World!"); // If you need an HTTP request with a content type: application/json, use the following: //https.addHeader("Content-Type", "application/json"); //int httpResponseCode = https.POST("{\"value1\":\"19\",\"value2\":\"67\",\"value3\":\"78\"}"); if (httpResponseCode>0) { Serial.print("HTTP Response code: "); Serial.println(httpResponseCode); } else { Serial.print("Error code: "); Serial.println(httpResponseCode); } // Free resources https.end(); } else { Serial.println("WiFi Disconnected"); } // Use deep sleep to make the ESP send an email every X number of minutes/hours with low power consumption // ESP32 Deep Sleep Guide: https://RandomNerdTutorials.com/esp32-deep-sleep-arduino-ide-wake-up-sources/ } void loop() { } View raw code

Setting your network credentials

You need to modify the following lines with your network credentials: SSID and password. The code is well commented on where you should make the changes. // Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Setting your serverName

You also need to type your domain name, so the ESP publishes the readings to your own server. const char* serverName = "https://example.com/email-notification.php"; Now, you can upload the code to your board. Note: Most servers require you to make HTTPS requests. The code above makes HTTPS requests to be compliant with the requirements of most cloud servers nowadays. Your server doesn't support HTTPS? Use this code instead.

How the code works

Here's a quick summary of how the code works: Import all the libraries to make it work (it will import either the ESP32 or ESP8266 libraries based on the selected board in your Arduino IDE); Set variables that you might want to change (apiKeyValue); The apiKeyValue is just a random string that you can modify. It's used for security reasons, so only anyone that knows your API key can send email notifications. Initialize the serial communication for debugging purposes; Establish a Wi-Fi connection with your router; Initialize the BME280 sensor to get temperature, humidity, and pressure readings; Initialize a secure WiFi client. Then, in the rest of the setup() is where you actually make the HTTP POST with the latest BME280 readings: // Your Domain name with URL path or IP address with path http.begin(client, serverName); // Specify content-type header http.addHeader("Content-Type", "application/x-www-form-urlencoded"); // Prepare your HTTP POST request data String httpRequestData = "api_key=" + apiKeyValue + "&value1=" + String(bme.readTemperature()) + "&value2=" + String(bme.readHumidity()) + "&value3=" + String(bme.readPressure()/100.0F) + ""; int httpResponseCode = http.POST(httpRequestData); You can comment the httpRequestData variable above that concatenates all the BME280 readings and use the httpRequestData variable below for testing purposes: String httpRequestData = "api_key=tPmAT5Ab3j7F9&value1=24.75&value2=49.54&value3=1005.14"; Learn more about HTTPS Requests with the ESP32: ESP32 HTTPS Requests (Arduino IDE).

ESP8266 Code

Follow this section if you're using an ESP8266. For an ESP32 check the section above. After installing the necessary board add-ons, copy the following code to your Arduino IDE, but don't upload it yet. You need to make some changes to make it work for you. /* Rui Santos Complete project details at https://RandomNerdTutorials.com/esp32-esp8266-send-email-notification/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ #include <ESP8266WiFi.h> #include <ESP8266HTTPClient.h> #include <WiFiClientSecureBearSSL.h> #include <Wire.h> #include <Adafruit_Sensor.h> #include <Adafruit_BME280.h> // Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; // REPLACE with your Domain name and URL path or IP address with path const char* serverName = "https://example.com/email-notification.php"; // Keep this API Key value to be compatible with the PHP code provided in the project page. // If you change the apiKeyValue value, the PHP file /email-notification.php also needs to have the same key String apiKeyValue = "tPmAT5Ab3j7F9"; /*#include <SPI.h> #define BME_SCK 18 #define BME_MISO 19 #define BME_MOSI 23 #define BME_CS 5*/ #define SEALEVELPRESSURE_HPA (1013.25) Adafruit_BME280 bme; // I2C //Adafruit_BME280 bme(BME_CS); // hardware SPI //Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // software SPI void setup() { Serial.begin(115200); WiFi.begin(ssid, password); Serial.println("Connecting"); while(WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.print("Connected to WiFi network with IP Address: "); Serial.println(WiFi.localIP()); // (you can also pass in a Wire library object like &Wire2) bool status = bme.begin(0x76); if (!status) { Serial.println("Could not find a valid BME280 sensor, check wiring or change I2C address!"); while (1); } //Check WiFi connection status if(WiFi.status()== WL_CONNECTED){ std::unique_ptr<BearSSL::WiFiClientSecure>client(new BearSSL::WiFiClientSecure); // Ignore SSL certificate validation client->setInsecure(); //create an HTTPClient instance HTTPClient https; // Your Domain name with URL path or IP address with path https.begin(*client, serverName); // Specify content-type header https.addHeader("Content-Type", "application/x-www-form-urlencoded"); // Prepare your HTTP POST request data String httpRequestData = "api_key=" + apiKeyValue + "&value1=" + String(bme.readTemperature()) + "&value2=" + String(bme.readHumidity()) + "&value3=" + String(bme.readPressure()/100.0F) + ""; Serial.print("httpRequestData: "); Serial.println(httpRequestData); // You can comment the httpRequestData variable above // then, use the httpRequestData variable below (for testing purposes without the BME280 sensor) //String httpRequestData = "api_key=tPmAT5Ab3j7F9&sensor=BME280&location=Office&value1=24.75&value2=49.54&value3=1005.14"; // Send HTTP POST request int httpResponseCode = https.POST(httpRequestData); // If you need an HTTP request with a content type: text/plain //http.addHeader("Content-Type", "text/plain"); //int httpResponseCode = https.POST("Hello, World!"); // If you need an HTTP request with a content type: application/json, use the following: //http.addHeader("Content-Type", "application/json"); //int httpResponseCode = https.POST("{\"value1\":\"19\",\"value2\":\"67\",\"value3\":\"78\"}"); if (httpResponseCode>0) { Serial.print("HTTP Response code: "); Serial.println(httpResponseCode); } else { Serial.print("Error code: "); Serial.println(httpResponseCode); } // Free resources https.end(); } else { Serial.println("WiFi Disconnected"); } // Use deep sleep to make the ESP send an email every X number of minutes/hours with low power consumption // ESP8266 Deep Sleep Guide: https://RandomNerdTutorials.com/esp8266-deep-sleep-with-arduino-ide/ } void loop() { } View raw code

Setting your network credentials

You need to modify the following lines with your network credentials: SSID and password. The code is well commented on where you should make the changes. // Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Setting your serverName

You also need to type your domain name, so the ESP publishes the readings to your own server. const char* serverName = "https://example.com/email-notification.php"; Now, you can upload the code to your board. Note: Most servers require you to make HTTPS requests. The code above makes HTTPS requests to be compliant with the requirements of most cloud servers nowadays. Your server doesn't support HTTPS? Use this code instead. Learn more about HTTPS Requests with the ESP8266: ESP8266 NodeMCU HTTPS Requests (Arduino IDE).

Demonstration

After completing all the steps, power your ESP board and let it make an HTTP request to your server: If everything is working properly, this is what you should see in your Arduino IDE Serial Monitor: Open your email client, you should have a new email with the subject [NEW] ESP BME280 Readings with the latest temperature readings: To send a new email, press the ESP on-board RESET/ENABLE button to restart it and new readings will be sent out via email. For a final application, I recommend using deep sleep to make the ESP send an email every X number of minutes/hours with low power consumption. Read one of these guides to add deep sleep to your sketch: ESP32 Deep Sleep with Arduino IDE and Wake Up Sources ESP8266 Deep Sleep with Arduino IDE

Enable Threshold Notification

Finally, keep in mind that every time you restart your ESP (or the ESP wakes from deep sleep), it will send a new email notification with the current values. It might be useful to set a threshold value so that you only receive an email notification when your readings are above or below the threshold. In your PHP script (email-notification.php), uncomment the following if statement: // Uncomment the next if statement to set a threshold // ($value1 = temperature, $value2 = humidity, $value3 = pressure) /*if($value1 < 24.0){ echo "Temperature below threshold, don't send email"; exit; }*/ It will look like this: // Uncomment the next if statement to set a threshold // ($value1 = temperature, $value2 = humidity, $value3 = pressure) if($value1 < 24.0){ echo "Temperature below threshold, don't send email"; exit; } You can modify that if statement with the value threshold and only send an email based on that condition. With the current code, it will only send an email notification when the temperature is above 24.0oC.

Wrapping Up

In this tutorial you've learned how to send emails with the ESP32 and ESP8266 using your own server and domain name. The example provided is as simple as possible so that you can understand how everything works. After understanding this example, you may change the email content, publish different sensor readings, use multiple ESP boards, and much more.

Visualize Your Sensor Readings from Anywhere in the World (ESP32/ESP8266 + MySQL + PHP)

In this project, you'll create a web page that displays sensor readings in a plot that you can access from anywhere in the world. In summary, you'll build an ESP32 or ESP8266 client that makes a request to a PHP script to publish sensor readings in a MySQL database. Updated on 26 March 2023 As an example, we'll be using a BME280 sensor connected to an ESP board. You can modify the code provided to send readings from a different sensor or use multiple boards. To create this project, you'll use these technologies: ESP32 or ESP8266 programmed with Arduino IDE Hosting server and domain name PHP script to insert data into MySQL database and display it on a web page MySQL database to store readings PHP script to plot data from database in charts You might also find helpful reading these projects: ESP32/ESP8266 Insert Data into MySQL Database using PHP and Arduino IDE ESP32/ESP8266 Plot Sensor Readings in Real Time Charts Web Server Table of Contents The project is divided into the following main sections:
    Hosting Your PHP Application and MySQL Database Preparing Your MySQL Database PHP Script HTTP POST Insert Data in MySQL Database PHP Script Visualize Database Content in a Chart Setting up the ESP32 or ESP8266

Watch the Video Demonstration

To see how the project works, you can watch the following video demonstration:

1. Hosting Your PHP Application and MySQL Database

The goal of this project is to have your own domain name and hosting account that allows you to store sensor readings from the ESP32 or ESP8266. You can visualize the readings from anywhere in the world by accessing your own server domain. Here's a high level overview of the project: I recommend using one of the following hosting services that can handle all the project requirements: Bluehost (user-friendly with cPanel): free domain name when you sign up for the 3-year plan. I recommend choosing the unlimited websites option; Digital Ocean: Linux server that you manage through a command line. I only recommended this option for advanced users. Those two services are the ones that I use and personally recommend, but you can use any other hosting service. Any hosting service that offers PHP and MySQL will work with this tutorial. If you don't have a hosting account, I recommend signing up for Bluehost. Get Hosting and Domain Name with Bluehost When buying a hosting account, you'll also have to purchase a domain name. This is what makes this project interesting: you'll be able to go your domain name (http://example.com) and see your ESP readings. If you like our projects, you might consider signing up to one of the recommended hosting services, because you'll be supporting our work. Note: you can also run a LAMP (Linux, Apache, MySQL, PHP) server on a Raspberry Pi to access data in your local network. However, the purpose of this tutorial is to publish readings in your own domain name that you can access from anywhere in the world. This allows you to easily access your ESP readings without relying on a third-party IoT platform.

2. Preparing Your MySQL Database

After signing up for a hosting account and setting up a domain name, you can login to your cPanel or similar dashboard. After that, follow the next steps to create your database, username, password and SQL table.

Creating a database and user

Open the Advanced tab: 1. Type database in the search bar and select MySQL Database Wizard. 2. Enter your desired Database name. In my case, the database name is esp_data. Then, press the Next Step button: Note: later you'll have to use the database name with the prefix that your host gives you (my database prefix in the screenshot above is blurred). I'll refer to it as example_esp_data from now on. 3. Type your Database username and set a password. You must save all those details, because you'll need them later to establish a database connection with your PHP code. That's it! Your new database and user were created successfully. Now, save all your details because you'll need them later: Database name: example_esp_data Username: example_esp_board Password: your password

Creating a SQL table

After creating your database and user, go back to cPanel dashboard and search for phpMyAdmin. In the left sidebar, select your database name example_esp_data and open the SQL tab. Important: make sure you've opened the example_esp_data database. Then, click the SQL tab. If you don't follow these exact steps and run the SQL query, you might create a table in the wrong database. Copy the SQL query in the following snippet: CREATE TABLE Sensor ( id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY, value1 VARCHAR(10), value2 VARCHAR(10), value3 VARCHAR(10), reading_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) View raw code Paste it in the SQL query field (highlighted with a red rectangle) and press the Go button to create your table: After that, you should see your newly created table called Sensor in the example_esp_data database as shown in the figure below:

3. PHP Script HTTP POST Insert Data in MySQL Database

In this section, we're going to create a PHP script that receives incoming requests from the ESP32 or ESP8266 and inserts the data into a MySQL database. If you're using a hosting provider with cPanel, you can search for File Manager: Then, select the public_html option and press the + File button to create a new .php file. Note: if you're following this tutorial and you're not familiar with PHP or MySQL, I recommend creating these exact files. Otherwise, you'll need to modify the ESP sketch provided with different URL paths. Create a new file in /public_html with this exact name and extension: post-data.php Edit the newly created file (post-data.php) and copy the following snippet: <?php /* Rui Santos Complete project details at https://RandomNerdTutorials.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ $servername = "localhost"; // REPLACE with your Database name $dbname = "REPLACE_WITH_YOUR_DATABASE_NAME"; // REPLACE with Database user $username = "REPLACE_WITH_YOUR_USERNAME"; // REPLACE with Database user password $password = "REPLACE_WITH_YOUR_PASSWORD"; // Keep this API Key value to be compatible with the ESP32 code provided in the project page. If you change this value, the ESP32 sketch needs to match $api_key_value = "tPmAT5Ab3j7F9"; $api_key = $value1 = $value2 = $value3 = ""; if ($_SERVER["REQUEST_METHOD"] == "POST") { $api_key = test_input($_POST["api_key"]); if($api_key == $api_key_value) { $value1 = test_input($_POST["value1"]); $value2 = test_input($_POST["value2"]); $value3 = test_input($_POST["value3"]); // Create connection $conn = new mysqli($servername, $username, $password, $dbname); // Check connection if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } $sql = "INSERT INTO Sensor (value1, value2, value3) VALUES ('" . $value1 . "', '" . $value2 . "', '" . $value3 . "')"; if ($conn->query($sql) === TRUE) { echo "New record created successfully"; } else { echo "Error: " . $sql . "<br>" . $conn->error; } $conn->close(); } else { echo "Wrong API Key provided."; } } else { echo "No data posted with HTTP POST."; } function test_input($data) { $data = trim($data); $data = stripslashes($data); $data = htmlspecialchars($data); return $data; } View raw code Before saving the file, you need to modify the $dbname, $username and $password variables with your unique details: // Your Database name $dbname = "example_esp_data"; // Your Database user $username = "example_esp_board"; // Your Database user password $password = "YOUR_USER_PASSWORD"; After adding the database name, username and password, save the file and continue with this tutorial. If you try to access your domain name in the next URL path, you'll see the message: http://example.com/post-data.php

4. PHP Script Visualize Database Content in a Chart

Create another PHP file in the /public_html directory that will plot the database content in a chart on a web page. Name your new file: esp-chart.php Edit the newly created file (esp-chart.php) and copy the following code: <!-- Rui Santos Complete project details at https://RandomNerdTutorials.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. --> <?php $servername = "localhost"; // REPLACE with your Database name $dbname = "REPLACE_WITH_YOUR_DATABASE_NAME"; // REPLACE with Database user $username = "REPLACE_WITH_YOUR_USERNAME"; // REPLACE with Database user password $password = "REPLACE_WITH_YOUR_PASSWORD"; // Create connection $conn = new mysqli($servername, $username, $password, $dbname); // Check connection if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } $sql = "SELECT id, value1, value2, value3, reading_time FROM Sensor order by reading_time desc limit 40"; $result = $conn->query($sql); while ($data = $result->fetch_assoc()){ $sensor_data[] = $data; } $readings_time = array_column($sensor_data, 'reading_time'); // ******* Uncomment to convert readings time array to your timezone ******** /*$i = 0; foreach ($readings_time as $reading){ // Uncomment to set timezone to - 1 hour (you can change 1 to any number) $readings_time[$i] = date("Y-m-d H:i:s", strtotime("$reading - 1 hours")); // Uncomment to set timezone to + 4 hours (you can change 4 to any number) //$readings_time[$i] = date("Y-m-d H:i:s", strtotime("$reading + 4 hours")); $i += 1; }*/ $value1 = json_encode(array_reverse(array_column($sensor_data, 'value1')), JSON_NUMERIC_CHECK); $value2 = json_encode(array_reverse(array_column($sensor_data, 'value2')), JSON_NUMERIC_CHECK); $value3 = json_encode(array_reverse(array_column($sensor_data, 'value3')), JSON_NUMERIC_CHECK); $reading_time = json_encode(array_reverse($readings_time), JSON_NUMERIC_CHECK); /*echo $value1; echo $value2; echo $value3; echo $reading_time;*/ $result->free(); $conn->close(); ?> <!DOCTYPE html> <html> <meta name="viewport" content="width=device-width, initial-scale=1"> <script src="https://code.highcharts.com/highcharts.js"></script> <style> body { min-width: 310px; max-width: 1280px; height: 500px; margin: 0 auto; } h2 { font-family: Arial; font-size: 2.5rem; text-align: center; } </style> <body> <h2>ESP Weather Station</h2> <div></div> <div></div> <div></div> <script> var value1 = <?php echo $value1; ?>; var value2 = <?php echo $value2; ?>; var value3 = <?php echo $value3; ?>; var reading_time = <?php echo $reading_time; ?>; var chartT = new Highcharts.Chart({ chart:{ renderTo : 'chart-temperature' }, title: { text: 'BME280 Temperature' }, series: [{ showInLegend: false, data: value1 }], plotOptions: { line: { animation: false, dataLabels: { enabled: true } }, series: { color: '#059e8a' } }, xAxis: { type: 'datetime', categories: reading_time }, yAxis: { title: { text: 'Temperature (Celsius)' } //title: { text: 'Temperature (Fahrenheit)' } }, credits: { enabled: false } }); var chartH = new Highcharts.Chart({ chart:{ renderTo:'chart-humidity' }, title: { text: 'BME280 Humidity' }, series: [{ showInLegend: false, data: value2 }], plotOptions: { line: { animation: false, dataLabels: { enabled: true } } }, xAxis: { type: 'datetime', //dateTimeLabelFormats: { second: '%H:%M:%S' }, categories: reading_time }, yAxis: { title: { text: 'Humidity (%)' } }, credits: { enabled: false } }); var chartP = new Highcharts.Chart({ chart:{ renderTo:'chart-pressure' }, title: { text: 'BME280 Pressure' }, series: [{ showInLegend: false, data: value3 }], plotOptions: { line: { animation: false, dataLabels: { enabled: true } }, series: { color: '#18009c' } }, xAxis: { type: 'datetime', categories: reading_time }, yAxis: { title: { text: 'Pressure (hPa)' } }, credits: { enabled: false } }); </script> </body> </html> View raw code After adding the $dbname, $username and $password save the file and continue with this project. // Your Database name $dbname = "example_esp_data"; // Your Database user $username = "example_esp_board"; // Your Database user password $password = "YOUR_USER_PASSWORD"; If you try to access your domain name in the following URL path, you'll see the following: https://example.com/esp-chart.php That's it! If you see three empty charts in your browser, it means that everything is ready. In the next section, you'll learn how to publish your ESP32 or ESP8266 sensor readings. To build the charts, we'll use the Highcharts library. We'll create three charts: temperature, humidity and pressure over time. The charts display a maximum of 40 data points, and a new reading is added every 30 seconds, but you change these values in your code.

5. Setting up the ESP32 or ESP8266

This project is compatible with both the ESP32 and ESP8266 boards. You just need to assemble a simple circuit and upload the sketch provided to insert temperature, humidity, pressure, and more into your database every 30 seconds. The sketch is slightly different for each board.

Parts Required

For this example, we'll get sensor readings from the BME280 sensor. Here's a list of parts you need to build the circuit for this project: ESP32 board (read Best ESP32 dev boards) Alternative ESP8266 board (read Best ESP8266 dev boards) BME280 sensor Jumper wires Breadboard You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Schematics

The BME280 sensor module we're using communicates via I2C communication protocol, so you need to connect it to the ESP32 or ESP8266 I2C pins.

BME280 wiring to ESP32

The ESP32 I2C pins are: GPIO 22: SCL (SCK) GPIO 21: SDA (SDI) So, assemble your circuit as shown in the next schematic diagram (read complete Guide for ESP32 with BME280). Recommended reading: ESP32 Pinout Reference Guide

BME280 wiring to ESP8266

The ESP8266 I2C pins are: GPIO 5 (D1): SCL (SCK) GPIO 4 (D2): SDA (SDI) Assemble your circuit as in the next schematic diagram if you're using an ESP8266 board (read complete Guide for ESP8266 with BME280). Recommended reading: ESP8266 Pinout Reference Guide

Installing Libraries

We'll program the ESP32/ESP8266 using Arduino IDE, so you must have the ESP32/ESP8266 add-on installed in your Arduino IDE. Follow one of the next tutorials depending on the board you're using: Install the ESP32 Board in Arduino IDE you also need to install the BME280 Library and Adafruit_Sensor library Install the ESP8266 Board in Arduino IDE you also need to install the BME280 Library and Adafruit_Sensor library

ESP32 Code

Follow this section if you're using an ESP32. For an ESP8266 click here. After installing the necessary board add-ons, copy the following code to your Arduino IDE, but don't upload it yet. You need to make some changes to make it work for you. /* Rui Santos Complete project details at https://RandomNerdTutorials.com/esp32-esp8266-mysql-database-php/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ #include <WiFi.h> #include <WiFiClientSecure.h> #include <HTTPClient.h> #include <Wire.h> #include <Adafruit_Sensor.h> #include <Adafruit_BME280.h> // Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; // REPLACE with your Domain name and URL path or IP address with path //const char* serverName = "https://example.com/post-data.php"; // Keep this API Key value to be compatible with the PHP code provided in the project page. // If you change the apiKeyValue value, the PHP file /post-esp-data.php also needs to have the same key String apiKeyValue = "tPmAT5Ab3j7F9"; /*#include <SPI.h> #define BME_SCK 18 #define BME_MISO 19 #define BME_MOSI 23 #define BME_CS 5*/ #define SEALEVELPRESSURE_HPA (1013.25) Adafruit_BME280 bme; // I2C //Adafruit_BME280 bme(BME_CS); // hardware SPI //Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // software SPI void setup() { Serial.begin(115200); WiFi.begin(ssid, password); Serial.println("Connecting"); while(WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.print("Connected to WiFi network with IP Address: "); Serial.println(WiFi.localIP()); // (you can also pass in a Wire library object like &Wire2) bool status = bme.begin(0x76); if (!status) { Serial.println("Could not find a valid BME280 sensor, check wiring or change I2C address!"); while (1); } } void loop() { //Check WiFi connection status if(WiFi.status()== WL_CONNECTED){ WiFiClientSecure *client = new WiFiClientSecure; client->setInsecure(); //don't use SSL certificate HTTPClient https; // Your Domain name with URL path or IP address with path https.begin(*client, serverName); // Specify content-type header https.addHeader("Content-Type", "application/x-www-form-urlencoded"); // Prepare your HTTP POST request data String httpRequestData = "api_key=" + apiKeyValue + "&value1=" + String(bme.readTemperature()) + "&value2=" + String(bme.readHumidity()) + "&value3=" + String(bme.readPressure()/100.0F) + ""; Serial.print("httpRequestData: "); Serial.println(httpRequestData); // You can comment the httpRequestData variable above // then, use the httpRequestData variable below (for testing purposes without the BME280 sensor) //String httpRequestData = "api_key=tPmAT5Ab3j7F9&sensor=BME280&location=Office&value1=24.75&value2=49.54&value3=1005.14"; // Send HTTP POST request int httpResponseCode = https.POST(httpRequestData); // If you need an HTTP request with a content type: text/plain //https.addHeader("Content-Type", "text/plain"); //int httpResponseCode = https.POST("Hello, World!"); // If you need an HTTP request with a content type: application/json, use the following: //https.addHeader("Content-Type", "application/json"); //int httpResponseCode = https.POST("{\"value1\":\"19\",\"value2\":\"67\",\"value3\":\"78\"}"); if (httpResponseCode>0) { Serial.print("HTTP Response code: "); Serial.println(httpResponseCode); } else { Serial.print("Error code: "); Serial.println(httpResponseCode); } // Free resources https.end(); } else { Serial.println("WiFi Disconnected"); } //Send an HTTP POST request every 30 seconds delay(30000); } View raw code

Setting your network credentials

You need to modify the following lines with your network credentials: SSID and password. The code is well commented on where you should make the changes. // Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Setting your serverName

You also need to type your domain name, so the ESP publishes the readings to your own server. const char* serverName = "https://example.com/post-data.php"; Now, you can upload the code to your board. Note: Most servers require you to make HTTPS requests. The code above makes HTTPS requests to be compliant with the requirements of most cloud servers nowadays. Your server doesn't support HTTPS? Use this code instead.

How the code works

This project is already quite long, so we won't cover in detail how the code works, but here's a quick summary: Import all the libraries to make it work; Set variables that you might want to change (apiKeyValue); The apiKeyValue is just a random string that you can modify. It's used for security reasons, so only anyone that knows your API key can publish data to your database; Initialize the serial communication for debugging purposes; Establish a Wi-Fi connection with your router; Initialize the BME280 to get readings; Initialize a secure WiFi client. Then, in the loop() is where you actually make the HTTP POST request every 30 seconds with the latest BME280 readings: // Your Domain name with URL path or IP address with path http.begin(serverName); // Specify content-type header http.addHeader("Content-Type", "application/x-www-form-urlencoded"); // Prepare your HTTP POST request data String httpRequestData = "api_key=" + apiKeyValue + "&value1=" + String(bme.readTemperature()) + "&value2=" + String(bme.readHumidity()) + "&value3=" + String(bme.readPressure()/100.0F) + ""; int httpResponseCode = http.POST(httpRequestData); You can comment the httpRequestData variable above that concatenates all the BME280 readings and use the httpRequestData variable below for testing purposes: String httpRequestData = "api_key=tPmAT5Ab3j7F9&value1=24.75&value2=49.54&value3=1005.14"; Learn more about HTTPS Requests with the ESP32: ESP32 HTTPS Requests (Arduino IDE).

ESP8266 Code

Follow this section if you're using an ESP8266. For an ESP32 check the section above. After installing the necessary board add-ons and libraries, copy the following code to your Arduino IDE, but don't upload it yet. You need to make some changes to make it work for you. /* Rui Santos Complete project details at https://RandomNerdTutorials.com/esp32-esp8266-mysql-database-php/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ #include <ESP8266WiFi.h> #include <ESP8266HTTPClient.h> #include <WiFiClientSecureBearSSL.h> #include <Wire.h> #include <Adafruit_Sensor.h> #include <Adafruit_BME280.h> // Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; // REPLACE with your Domain name and URL path or IP address with path const char* serverName = "https://example.com/post-data.php"; // Keep this API Key value to be compatible with the PHP code provided in the project page. // If you change the apiKeyValue value, the PHP file /post-esp-data.php also needs to have the same key String apiKeyValue = "tPmAT5Ab3j7F9"; /*#include <SPI.h> #define BME_SCK 18 #define BME_MISO 19 #define BME_MOSI 23 #define BME_CS 5*/ #define SEALEVELPRESSURE_HPA (1013.25) Adafruit_BME280 bme; // I2C //Adafruit_BME280 bme(BME_CS); // hardware SPI //Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // software SPI void setup() { Serial.begin(115200); WiFi.begin(ssid, password); Serial.println("Connecting"); while(WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.print("Connected to WiFi network with IP Address: "); Serial.println(WiFi.localIP()); // (you can also pass in a Wire library object like &Wire2) bool status = bme.begin(0x76); if (!status) { Serial.println("Could not find a valid BME280 sensor, check wiring or change I2C address!"); while (1); } } void loop() { //Check WiFi connection status if(WiFi.status()== WL_CONNECTED){ std::unique_ptr<BearSSL::WiFiClientSecure>client(new BearSSL::WiFiClientSecure); // Ignore SSL certificate validation client->setInsecure(); //create an HTTPClient instance HTTPClient https; // Your Domain name with URL path or IP address with path https.begin(*client, serverName); // Specify content-type header https.addHeader("Content-Type", "application/x-www-form-urlencoded"); // Prepare your HTTP POST request data String httpRequestData = "api_key=" + apiKeyValue + "&value1=" + String(bme.readTemperature()) + "&value2=" + String(bme.readHumidity()) + "&value3=" + String(bme.readPressure()/100.0F) + ""; Serial.print("httpRequestData: "); Serial.println(httpRequestData); // You can comment the httpRequestData variable above // then, use the httpRequestData variable below (for testing purposes without the BME280 sensor) //String httpRequestData = "api_key=tPmAT5Ab3j7F9&sensor=BME280&location=Office&value1=24.75&value2=49.54&value3=1005.14"; // Send HTTP POST request int httpResponseCode = https.POST(httpRequestData); // If you need an HTTP request with a content type: text/plain //http.addHeader("Content-Type", "text/plain"); //int httpResponseCode = https.POST("Hello, World!"); // If you need an HTTP request with a content type: application/json, use the following: //http.addHeader("Content-Type", "application/json"); //int httpResponseCode = https.POST("{\"value1\":\"19\",\"value2\":\"67\",\"value3\":\"78\"}"); if (httpResponseCode>0) { Serial.print("HTTP Response code: "); Serial.println(httpResponseCode); } else { Serial.print("Error code: "); Serial.println(httpResponseCode); } // Free resources https.end(); } else { Serial.println("WiFi Disconnected"); } //Send an HTTP POST request every 30 seconds delay(30000); } View raw code

Setting your network credentials

You need to modify the following lines with your network credentials: SSID and password. The code is well commented on where you should make the changes. // Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Setting your serverName

You also need to type your domain name, so the ESP publishes the readings to your own server. const char* serverName = "https://example.com/post-data.php"; Now, you can upload the code to your board. rnt_box type=3] Most servers require you to make HTTPS requests. The code above makes HTTPS requests to be compliant with the requirements of most cloud servers nowadays. Your server doesn't support HTTPS? Use this code instead. Learn more about HTTPS Requests with the ESP8266: ESP8266 NodeMCU HTTPS Requests (Arduino IDE).

Demonstration

After completing all the steps, let your ESP board collect some readings and publish them to your server. If everything is correct, this is what you should see in your Arduino IDE Serial Monitor: If you open your domain name in this URL path: https://example.com/esp-chart.php You should see all the readings stored in your database. Refresh the web page to see the latest readings: You can also go to phpMyAdmin to manage the data stored in your Sensor table. You can delete it, edit, etc

Wrapping Up

In this tutorial, you learned how to publish sensor data into a database in your own server domain that you can access from anywhere in the world. This requires that you have your own server and domain name (you can also use a Raspberry Pi for local access). With this setup, you control your server and can move to a different host if necessary. There are many cloud solutions both free and paid that you can use to publish your readings, but those services can have several disadvantages: restrictions on how many readings you can publish, the number of connected devices, who can see your data, etc. Additionally, the cloud service can be discontinued or change at any time. The example provided is as simple as possible so that you can understand how everything works. After understanding this example, you may change the web page appearance, publish different sensor readings, publish from multiple ESP boards, and much more.

Erase Flash Memory (Factory Reset)

This is a quick guide showing how to erase the ESP32 flash memory to restore it to its original state. This might be useful if you want to delete any changes made to the firmware or configuration settings; if the system is crashing constantly and you can't upload new code; to clear data that is no longer needed, and other applications. We'll use a tool called esptool.

Installing esptool.py

To perform an ESP32 factory reset, we'll use esptool, which is a Python-based, open-source, platform-independent utility to communicate with the ROM bootloader in Espressif chips. To install esptool, you need Python 3.7 or newer installed on your system. You can download and install Python at the following link (make sure you download the right package for your system): Download Python With Python 3 installed, open a Terminal window and install the latest stable esptool.py release with pip: pip install esptool Note: with some Python installations that command may not work and you'll receive an error. If that's the case, try to install esptool.py with: pip3 install esptool python -m pip install esptool pip2 install esptool Setuptools is also a requirement that is not available on all systems by default. You can install it with the following command: pip install setuptools After installing, you will have esptool.py installed into the default Python executables directory and you should be able to run it with the command esptool. In your Terminal window, run the following command: python -m esptool If it was installed properly, it should display a similar message (regardless of your operating system):

Erasing the ESP32 Flash

Follow the next steps to erase the ESP32 flash: 1) Connect the ESP32 to your computer; 2) Open a Terminal window on your computer; 3) Hold the ESP32 BOOT button; 4) Copy the following command to your terminal window and press Enter (continue holding the BOOT button). python -m esptool --chip esp32 erase_flash 5) When the Erasing process begins, you can release the BOOT/FLASH button. After a few seconds, the ESP32 flash memory will be erased. Note: if after the Connecting message you keep seeing new dots appearing, it means that your ESP32 is not in flashing mode. You need to repeat all the steps described earlier and hold the BOOT/FLASH button again to ensure that your ESP32 goes into flashing mode and completes the erasing process successfully.

Troubleshooting

If you encounter a permission error while trying to run the esptool command, open the command prompt as a administrator (or as sudo on Linux).

Wrapping Up

This was a quick guide showing you how to erase the ESP32 flash to perform a factory reset. We hope this tutorial is useful. If you're using an ESP8266 board, you can follow the instructions in the following tutorial: ESP8266 NodeMCU: Erase Flash Memory (Factory Reset)

Send BME280 Sensor Readings to InfluxDB

In this guide, you'll learn how to send BME280 sensor readings to InfluxDB using the ESP32 or ESP8266 boards. InfluxDB is a time series database. Each record on the database is associated with a timestamp, which makes it ideal for datalogging IoT and Home Automation projects. InfluxDB also provides dashboard tools to visualize data in different formats like charts, gauges, histograms, etc. By the end of this tutorial, you'll be able to build a dashboard as shown below to display all your sensor readings over time. To get started with InfluxDB, read one of the following guides: ESP32: Getting Started with InfluxDB ESP8266 NodeMCU: Getting Started with InfluxDB

What is InfluxDB?

InfluxDB is an open-source high-performance time series database (TSDB) that can store large amounts of data per second. Each data point you submit to the database is associated with a particular timestamp. So, it is ideal for IoT datalogging projects like storing data from your weather station sensors. You can run InfluxDB in InfluxDB Cloud, or locally on your laptop or Raspberry Pi.
Why use InfluxDB Cloud? It's a fast, elastic, serverless real-time monitoring platform, dashboarding engine, analytics service and event and metrics processor. https://www.influxdata.com/products/influxdb-cloud/
For a more in-depth introduction to InfluxDB, check the following tutorials before proceeding: ESP32: Getting Started with InfluxDB ESP8266: Getting Started with InfluxDB

Creating an InfluxDB Account

If you don't have an InfluxDB account, follow the next steps. If you already have an account, you can skip to the next section. If you want to run InfluxDB locally on a Raspberry Pi, follow the next tutorial first: Install InfluxDB 2 on Raspberry Pi. Then, proceed to the Loading Data in InfluxDB section. 1) Go to https://cloud2.influxdata.com/signup and create an InfluxDB account. 2) Select where you want to save your data. I selected the Google Cloud option, but you can select one of the other options. Select the region, it should be the closest to your location. Then, enter something on the Company Name field, and agree to the terms before proceeding. 3) Select the InfluxDB plan. For this example, we'll be using the Free plan. For most of our IoT projects, the Free plan works just fine. 4) After selecting the plan, you'll be redirected to the Getting Started page.

Loading Data in InfluxDB

5) Click on Load Data icon and select Sources. 6) Scroll down until you find the Arduino option under the Client Libraries section. Click on Initialize Client. The page that opens allows you to create buckets, and it also shows some sample code to interface the ESP8266 or ESP32 boards with InfluxDB.

Creating an InfluxDB Bucket

7) Create a new bucket to store your data. Click on + Create Bucket to create a new bucket for this example. You can use the settings by default, or you can customize them.

Getting InfluxDB URL and API Token

8) Get your InfluxDB URL* and other details you'll need later. Scroll down to the Configure InfluxDB profile snippet. Then, copy the INFLUXDB_URL, INFLUXDB_TOKEN, INFLUXDB_ORG, and INFLUXDB_BUCKET. * if you're running InfluxDB locally on a Raspberry Pi, the URL will be the Raspberry Pi IP address on port 8086. For example 192.168.1.106:8086.

API Tokens

If you've followed the previous steps, InfluxDB cloud has already created an API token for you that you could find in the snippet presented in the previous step. If you go to the Load Data icon and select API Tokens, you'll find the previously generated API token. On this page, you can generate a new API token if needed. At this moment, you should have saved the following: InfluxDB Server URL InfluxDB Organization InfluxDB Bucket Name API Token

ESP32/ESP8266 Send Sensor Readings to InfluxDB

In this section, we'll program the ESP32 and ESP8266 boards to send BME280 temperature, humidity, and pressure readings to InfluxDB.

Parts Required

For this project, you need the following parts*: ESP32 or ESP8266 board (read ESP32 vs ESP8266); BME280 or any other sensor you're familiar with; Breadboard; Jumper wires. * you can also test the project with random values instead of sensor readings, or you can use any other sensor you're familiar with. You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Schematic Diagram

In this tutorial, we'll send BME280 sensor readings to InfluxDB. So, you need to wire the BME280 sensor to your board. Follow one of the next schematic diagrams.

ESP32 with BME280

We're going to use I2C communication with the BME280 sensor module. Wire the sensor to the default ESP32 SCL (GPIO 22) and SDA (GPIO 21) pins, as shown in the following schematic diagram. Not familiar with the BME280 with the ESP32? Read this tutorial: ESP32 with BME280 Sensor using Arduino IDE (Pressure, Temperature, Humidity).

ESP8266 with BME280

We're going to use I2C communication with the BME280 sensor module. For that, wire the sensor to the ESP8266 SDA (GPIO 4) and SCL (GPIO 5) pins, as shown in the following schematic diagram. Not familiar with the BME280 with the ESP8266? Read this tutorial: ESP8266 with BME280 using Arduino IDE (Pressure, Temperature, Humidity).

Installing Libraries

For this project, you need to install the following libraries: InfluxDB Arduino Client Library Adafruit BME280 Library Adafruit Unified Sensor Library

Installation Arduino IDE

If you're using Arduino IDE, follow the next steps to install the library.
    Go to Sketch > Include Library > Manage Libraries Search for InfluxDB and install the ESP8266 Influxdb library by Tobias Shrg.
Note: we are using ESP8266 Boards version 3.0.1 and ESP32 Boards version 2.0.1 Check your boards' version in Tools > Board > Boards Manager. Then, search for ESP8266 or ESP32 and upgrade to one of those versions. (I found some issues with the newest 3.0.2 and 2.0.2 versions)
    Follow the same instructions to install the Adafruit BME280 Library and Adafruit Unified Sensor Library.

Installation VS Code

If you're using VS Code with the PlatformIO extension, start by creating an Arduino project for your ESP32 or ESP8266 board. Then, click on the PIO Home icon and then select the Libraries tab. Search for influxdb. Select the ESP8266 Influxdb by Tobias Schrg. Follow the same procedure for the Adafruit BME280 library and Adafruit Unified Sensor library. Your plaformio.ini file should look as follows (we also added a line to change the Serial Monitor baud rate to 115200). monitor_speed = 115200 lib_deps = tobiasschuerg/ESP8266 Influxdb@^3.12.0 adafruit/Adafruit BME280 Library@^2.2.2 adafruit/Adafruit Unified Sensor@^1.1.5

Send Sensor Readings to InfluxDBCode

Copy the following code to the Arduino IDE or to the main.cpp file if you're using VS Code with the PlatformIO extension. The code is compatible with both the ESP32 and ESP8266 boards. This code is based on this library example. We made a few modifications to include the BME280 sensor readings. /* Rui Santos Complete project details at our blog: https://RandomNerdTutorials.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ // Based on this library example: https://github.com/tobiasschuerg/InfluxDB-Client-for-Arduino/blob/master/examples/SecureBatchWrite/SecureBatchWrite.ino #include <Arduino.h> #include <Wire.h> #include <Adafruit_Sensor.h> #include <Adafruit_BME280.h> #if defined(ESP32) #include <WiFiMulti.h> WiFiMulti wifiMulti; #define DEVICE "ESP32" #elif defined(ESP8266) #include <ESP8266WiFiMulti.h> ESP8266WiFiMulti wifiMulti; #define DEVICE "ESP8266" #define WIFI_AUTH_OPEN ENC_TYPE_NONE #endif #include <InfluxDbClient.h> #include <InfluxDbCloud.h> // WiFi AP SSID #define WIFI_SSID "REPLACE_WITH_YOUR_SSID" // WiFi password #define WIFI_PASSWORD "REPLACE_WITH_YOUR_PASSWORD" // InfluxDB v2 server url, e.g. https://eu-central-1-1.aws.cloud2.influxdata.com (Use: InfluxDB UI -> Load Data -> Client Libraries) #define INFLUXDB_URL "REPLACE_WITH_YOUR_DATABASE_URL" // InfluxDB v2 server or cloud API authentication token (Use: InfluxDB UI -> Load Data -> Tokens -> <select token>) #define INFLUXDB_TOKEN "REPLACE_WITH_YOUR_TOKEN" // InfluxDB v2 organization id (Use: InfluxDB UI -> Settings -> Profile -> <name under tile> ) #define INFLUXDB_ORG "REPLACE_WITH_YOUR_ORG" // InfluxDB v2 bucket name (Use: InfluxDB UI -> Load Data -> Buckets) #define INFLUXDB_BUCKET "SENSOR" // Set timezone string according to https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html // Examples: // Pacific Time: "PST8PDT" // Eastern: "EST5EDT" // Japanesse: "JST-9" // Central Europe: "CET-1CEST,M3.5.0,M10.5.0/3" #define TZ_INFO "WET0WEST,M3.5.0/1,M10.5.0" // InfluxDB client instance with preconfigured InfluxCloud certificate InfluxDBClient client(INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN, InfluxDbCloud2CACert); // InfluxDB client instance without preconfigured InfluxCloud certificate for insecure connection //InfluxDBClient client(INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN); // Data point Point sensorReadings("measurements"); //BME280 Adafruit_BME280 bme; // I2C float temperature; float humidity; float pressure; // Initialize BME280 void initBME(){ if (!bme.begin(0x76)) { Serial.println("Could not find a valid BME280 sensor, check wiring!"); while (1); } } void setup() { Serial.begin(115200); // Setup wifi WiFi.mode(WIFI_STA); wifiMulti.addAP(WIFI_SSID, WIFI_PASSWORD); Serial.print("Connecting to wifi"); while (wifiMulti.run() != WL_CONNECTED) { Serial.print("."); delay(500); } Serial.println(); //Init BME280 sensor initBME(); // Add tags sensorReadings.addTag("device", DEVICE); sensorReadings.addTag("location", "office"); sensorReadings.addTag("sensor", "bme280"); // Accurate time is necessary for certificate validation and writing in batches // For the fastest time sync find NTP servers in your area: https://www.pool.ntp.org/zone/ // Syncing progress and the time will be printed to Serial. timeSync(TZ_INFO, "pool.ntp.org", "time.nis.gov"); // Check server connection if (client.validateConnection()) { Serial.print("Connected to InfluxDB: "); Serial.println(client.getServerUrl()); } else { Serial.print("InfluxDB connection failed: "); Serial.println(client.getLastErrorMessage()); } } void loop() { // Get latest sensor readings temperature = bme.readTemperature(); humidity = bme.readHumidity(); pressure = bme.readPressure()/100.0F; // Add readings as fields to point sensorReadings.addField("temperature", temperature); sensorReadings.addField("humidity", humidity); sensorReadings.addField("pressure", pressure); // Print what are we exactly writing Serial.print("Writing: "); Serial.println(client.pointToLineProtocol(sensorReadings)); // Write point into buffer client.writePoint(sensorReadings); // Clear fields for next usage. Tags remain the same. sensorReadings.clearFields(); // If no Wifi signal, try to reconnect it if (wifiMulti.run() != WL_CONNECTED) { Serial.println("Wifi connection lost"); } // Wait 10s Serial.println("Wait 10s"); delay(10000); } View raw code Before uploading the code to your board, you need to insert your network credentials, the database URL, token, organization, and bucket name. Also, don't forget to insert your timezone. Check this document to search for your timezone in the right format.

How the Code Works

Start by including the required libraries and setting the DEVICE name accordingly to the selected board (ESP32 or ESP8266). #include <Arduino.h> #include <Wire.h> #include <Adafruit_Sensor.h> #include <Adafruit_BME280.h> #if defined(ESP32) #include <WiFiMulti.h> WiFiMulti wifiMulti; #define DEVICE "ESP32" #elif defined(ESP8266) #include <ESP8266WiFiMulti.h> ESP8266WiFiMulti wifiMulti; #define DEVICE "ESP8266" #define WIFI_AUTH_OPEN ENC_TYPE_NONE #endif #include <InfluxDbClient.h> #include <InfluxDbCloud.h> Insert your network credentials on the following variables so that the ESP32 or ESP8266 boards can connect to the internet using your local network. // WiFi AP SSID #define WIFI_SSID "REPLACE_WITH_YOUR_SSID" // WiFi password #define WIFI_PASSWORD "REPLACE_WITH_YOUR_PASSWORD" Insert the InfluxDB server URL on the following linesthe one you've gotten in this step: // InfluxDB v2 server url, e.g. https://eu-central-1-1.aws.cloud2.influxdata.com (Use: InfluxDB UI -> Load Data -> Client Libraries) #define INFLUXDB_URL "REPLACE_WITH_YOUR_INFLUXDB_URL" Note: if you're running InfluxDB locally on a Raspberry Pi, the URL will be the Raspberry Pi IP address on port 8086. For example 192.168.1.106:8086. Insert your InfluxDB tokensaved in this step: #define INFLUXDB_TOKEN "REPLACE_WITH_YOUR_INFLUXDB_TOKEN" Add your InfluxDB organization namecheck this step. #define INFLUXDB_ORG "REPLACE_WITH_YOUR_INFLUXXDB_ORGANIZATION_ID" Finally, add your InfluxDB bucket name: #define INFLUXDB_BUCKET "REPLACE_WITH_YOUR_BUCKET_NAME"

Setting your Timezone

You must set your timezone accordingly to these instructions. The easiest way is to check this table and copy your timezone from there. In my case, it's Lisbon timezone: #define TZ_INFO "WET0WEST,M3.5.0/1,M10.5.0"

InfluxDB Client

Now that you have all the required settings, you can create an InfluxDBClient instance. We're creating a secure client that uses a preconfigured certificatelearn more about secure connection. InfluxDBClient client(INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN, InfluxDbCloud2CACert);

Point

Then, we create a Point instance called sensorReadings. The point will be called measurements on the database. Later in the code, we can refer to that point (sensorReadings) to add tags and fields. Point sensorReadings("measurements"); Note: A set of data in a database row is known as point. Each point has a measurement, a tag set, a field key, a field value, and a timestamp.

BME280 Variables

Create an Adafruit_BME280 instance called bme on the default ESP I2C pins. Adafruit_BME280 bme; // I2C Create variables to save the temperature, humidity, and pressure readings. float temperature; float humidity; float pressure;

initBME() function

The initBME() function initializes the BME280 sensor. We'll call it later in the setup() to initialize the sensor. // Initialize BME280 void initBME(){ if (!bme.begin(0x76)) { Serial.println("Could not find a valid BME280 sensor, check wiring!"); while (1); } }

setup()

In the setup(), initialize the Serial Monitor for debugging purposes. Serial.begin(115200); Initialize Wi-Fi. // Setup wifi WiFi.mode(WIFI_STA); wifiMulti.addAP(WIFI_SSID, WIFI_PASSWORD); Serial.print("Connecting to wifi"); while (wifiMulti.run() != WL_CONNECTED) { Serial.print("."); delay(500); } Serial.println(); Call the initBME() function to initialize the BME280 sensor. //Init BME280 sensor initBME(); Add tags to your data using addTag(). We're adding the device name, the name of the sensor, and the location of the sensor. You may add other tags that might be useful for your specific project. sensorReadings.addTag("device", DEVICE); sensorReadings.addTag("location", "office"); sensorReadings.addTag("sensor", "bme280"); Synchronize time, which is necessary for certificate validation. timeSync(TZ_INFO, "pool.ntp.org", "time.nis.gov"); Check the connection to the InfluxDB server and print any error messages in case the connection fails: // Check server connection if (client.validateConnection()) { Serial.print("Connected to InfluxDB: "); Serial.println(client.getServerUrl()); } else { Serial.print("InfluxDB connection failed: "); Serial.println(client.getLastErrorMessage()); }

loop()

In the loop(), we'll send the sensor readings to InfluxDB every 10 seconds. We get temperature, humidity, and pressure readings: temperature = bme.readTemperature(); humidity = bme.readHumidity(); pressure = bme.readPressure()/100.0F; And we add those readings as fields to our point (database data row). // Add readings as fields to point sensorReadings.addField("temperature", temperature); sensorReadings.addField("humidity", humidity); sensorReadings.addField("pressure", pressure); Print in the Serial Monitor what we're writing to the point: Serial.println(client.pointToLineProtocol(sensorReadings)); Finally, to actually add the point to the database, we use the writePoint() method on the InfluxDBClient object and pass as argument the point we want to add: client.writePoint(sensorReadings). client.writePoint(sensorReadings); Clear the fields to be ready for usage in the next loop. sensorReadings.clearFields(); New data is written to the database every 10 seconds. Serial.println("Wait 10s"); delay(10000);

DemonstrationVisualizing your Data

After inserting all the required settings on the code, you can upload it to your ESP32/ESP8266 board. If you get any error during compilation, check the following: check that you have an ESP32/ESP8266 board selected in Tools > Board. Check your ESP32/ESP8266 boards installation version in Tools > Board > Boards Manager > ESP8266 or ESP32. Select version 3.0.1 for ESP8266 or version 2.01 for ESP32 if you're getting issues with other versions. After uploading the code to your board, open the Serial Monitor at a baud rate of 115200. Press the ESP on-board RESET button to restart the board. It should print something similar on the Serial Monitor: Now, go to your InfludDB account and go to the Data Explorer by clicking on the corresponding icon. Now, you can visualize your data. Start by selecting the bucket you want. Then, we need to add filters to select our data. Select the measurement under the _measurement field, the device and the location. Then, you can select the temperature, humidity, or pressure values. You can also select all readings if you want to plot them all on the same chart. After making the query, click on the SUBMIT button. This will display your data in your chosen format. In the upper left corner, you can select different ways to visualize the data. You can also click on the CUSTOMIZE button to change the color of the series. You can create a dashboard to show multiple data visualizations in different formats (gauges, histogram, single stat, etc.) or different data on the same page. For example, multiple charts to show temperature, humidity, and pressure, and boxes to show the current measurements.

Creating a Dashboard

Click on the Dashboard icon. Then on Create Dashboard > New dashboard. Add a cell. Make the query to get your data and select the visualization you want. Give a name to the cell, for example, Office Temperature(ESP32). You can also click on the Customize button to customize the graph (we suggest selecting different colors for temperature, humidity, and pressure). Finally, click on the icon in the top right corner to add the visualization as a cell to your dashboard. Repeat the same process for the other readings (humidity, and pressure). You can also add a single stat to show the current values of each reading. I have the ESP32 and ESP8266 running the same code simultaneously, so I created a dashboard that shows the readings of each board. You can move your cells to different positions and organize the dashboard in a way that makes sense for you. You can also customize the way the data is refreshed and how many data points you want to see (up to the past 30 days).

Wrapping Up

In this tutorial, you learned how to send multiple sensor readings to InfluxDB. InfluxDB adds a timestamp to all data rows (point). As an example, we used a BME280 sensor, but you can easily modify the example to use any other sensor or add more sensors. You can also run this example on multiple boards to monitor the temperature, humidity, and pressure in different placesdon't forget to add different tags to identify the places or the boards.

Getting Started with InfluxDB

This guide will get you started quickly with InfluxDB using the ESP32 board. InfluxDB is an open-source time series database (TSDB). So, it is ideal to store sensor data with timestamps over a determined period of time. In this tutorial, you'll set up an InfluxDB bucket to save data, and you'll learn how to save ESP32 data to the database. InfluxDB also offers different ways to visualize and organize your data (dashboards, charts, tables, gauges, etc). We have a similar tutorial for the ESP8266 NodeMCU board: Getting Started with InfluxDB Table of Contents Introducing InfluxDB Creating an InfluxDB Account Loading Data in InfluxDB Interfacing the ESP32 with InfluxDB Visualizing Data on InfluxDB

What is InfluxDB?

InfluxDB is an open-source high-performance time series database (TSDB) that can store large amounts of data per second. Each data point you submit to the database is associated with a particular timestamp. So, it is ideal for IoT datalogging projects like storing data from your weather station sensors. You can run InfluxDB in InfluxDB Cloud, or locally on your laptop or Raspberry Pi.
Why use InfluxDB Cloud? It's a fast, elastic, serverless real-time monitoring platform, dashboarding engine, analytics service and event and metrics processor. https://www.influxdata.com/products/influxdb-cloud/

InfluxDB Key Terms

Before getting started, there are some important terms you need to understand. We'll just take a look at the most relevant terms, you can read the complete glossary. Don't worry if some of the terms are confusing. You'll better understand those terms when you start writing to the database. In InfluDB, a bucket is named location where the time series data is stored. All buckets have a retention periodit defines the duration of time that a bucket retains data. Points with timestamps older than the retention period are dropped. Data in InfluxDB is stored in tables within rows and columns. A set of data in a row is known as point (similar to a row in a SQL database table). Each point has a measurement, a tag set, a field key, a field value, and a timestamp; Columns store tag sets (indexed) and fields sets. The only required column is time, which stores timestamps and is included in all InfluxDB tables. tag: the key-value pair in InfluxDB's data structure that records metadata. Tags are an optional part of InfluxDB's data structure but they are useful for storing commonly-queried metadata; tags are indexed so queries on tags are performant. tag key: tag keys are strings and store metadata. Tag keys are indexed so queries on tag keys are processed quickly. tag value: tag values are strings and they store metadata. Tag values are indexed so queries on tag values are processed quickly. field: the key-value pair in InfluxDB's data structure that records metadata and the actual data value. Fields are required in InfluxDB's data structure and they are not indexed queries on field values scan all points that match the specified time range and, as a result, are not performant relative to tags. field key: the key of the key-value pair. Field keys are strings and they store metadata. field value: the value of a key-value pair. Field values are the actual data; they can be strings, floats, integers, or booleans. A field value is always associated with a timestamp. Field values are not indexed queries on field values scan all points that match the specified time range and, as a result, are not performant. measurement: the part of InfluxDB's structure that describes the data stored in the associated fields. We also recommend taking a quick look at the InfluxDB key concepts.

Creating an InfluxDB Account

If you want to run InfluxDB locally on a Raspberry Pi, follow the next tutorial first: Install InfluxDB 2 on Raspberry Pi. Then, proceed to the Loading Data in InfluxDB section. 1) Go to https://cloud2.influxdata.com/signup and create an InfluxDB account. 2) Select where you want to save your data. I choose the Google Cloud option, but you can choose one of the other options. Select the region, it should be the closest to your location. Then, enter something on the Company Name field, and agree to the terms before proceeding. 3) Select the InfluxDB plan. For this example, we'll be using the Free plan. For most of our IoT projects, the Free plan works just fine. 4) After selecting the plan, you'll be redirected to the Getting Started page.

Loading Data in InfluxDB

5) Click on the Load Data icon and select Sources. 6) Scroll down until you find the Arduino option under the Client Libraries section. Click on Initialize Client. The page that opens allows you to create buckets, and it also shows some sample code to interface the ESP8266 or ESP32 boards with InfluxDB.

Creating an InfluxDB Bucket

7) Create a new bucket to store your data. Click on + Create Bucket to create a new bucket for this example. You can use the settings by default, or you can customize them.

Getting InfluxDB URL and API Token

8) Get your InfluxDB URL* and other details you'll need later. Scroll down to the Configure InfluxDB profile snippet. Then, copy the INFLUXDB_URL, INFLUXDB_TOKEN, INFLUXDB_ORG, and INFLUXDB_BUCKET. * if you're running InfluxDB locally on a Raspberry Pi, the URL will be the Raspberry Pi IP address on port 8086. For example 192.168.1.106:8086.

API Tokens

If you've followed the previous steps, InfluxDB cloud has already created an API token for you that you could find in the snippet presented in the previous step. If you go to the Load Data icon and select API Tokens, you'll find the previously generated API token. On this page, you can generate a new API token if needed. At this moment, you should have saved the following: InfluxDB Server URL InfluxDB Organization InfluxDB Bucket Name API Token

Interfacing the ESP32 with InfluxDB

We'll program the ESP32 using Arduino IDe, so make sure you have the ESP32 boards add-on installed: Installing the ESP32 Board in Arduino IDE (Windows, Mac OS X, Linux) Alternatively, you can use VS Code with the PlatformIO extension. Check the following tutorial to get started with VS Code + PlatformIO: Getting Started with VS Code and PlatformIO IDE for ESP32 and ESP8266

Install the InfluxDB Arduino Client Library

There is a library that makes it easy to interface the ESP32 with InfluxDB: the InfluxDB Arduino Client Library. This library is also compatible with ESP8266 boards.

Installation Arduino IDE

If you're using Arduino IDE, follow the next steps to install the library.
    Go to Sketch > Include Library > Manage Libraries Search for InfluxDB and install the ESP8266 Influxdb library by Tobias Shrg (even though it has ESP8266 in the name, it is also compatible with the ESP32).
Note: we are using ESP32 Boards version 2.0.1. Check your boards version in Tools > Board > Boards Manager. Then, search for ESP32 and upgrade to version 2.0.1 (at the moment there are some issues with version 2.0.2, so don't select that one).

Installation VS Code

If you're using VS Code with the PlatformIO extension, start by creating an Arduino project for your ESP32 board. Then, click on the PIO Home icon and then select the Libraries tab. Search for influxdb. Select the ESP8266 Influxdb byt Tobias Schrg.

ESP32 Save Data in InfluxDB

To show you how to save data to InfluxDB using the ESP32, we'll take a look at one of the examples provided by the library. In your Arduino IDE, go to File > Examples > ESP8266 Infuxdb > Secure Write. Or simply copy the code below to your Arduino IDE. /** * Secure Write Example code for InfluxDBClient library for Arduino * Enter WiFi and InfluxDB parameters below * * Demonstrates connection to any InfluxDB instance accesible via: * - unsecured http://... * - secure https://... (appropriate certificate is required) * - InfluxDB 2 Cloud at https://cloud2.influxdata.com/ (certificate is preconfigured) * Measures signal level of the actually connected WiFi network * This example demonstrates time handling, secure connection and measurement writing into InfluxDB * Data can be immediately seen in a InfluxDB 2 Cloud UI - measurement wifi_status * * Complete project details at our blog: https://RandomNerdTutorials.com/ * **/ #if defined(ESP32) #include <WiFiMulti.h> WiFiMulti wifiMulti; #define DEVICE "ESP32" #elif defined(ESP8266) #include <ESP8266WiFiMulti.h> ESP8266WiFiMulti wifiMulti; #define DEVICE "ESP8266" #endif #include <InfluxDbClient.h> #include <InfluxDbCloud.h> // WiFi AP SSID #define WIFI_SSID "REPLACE_WITH_YOUR_SSID" // WiFi password #define WIFI_PASSWORD "REPLACE_WITH_YOUR_PASSWORD" // InfluxDB v2 server url, e.g. https://eu-central-1-1.aws.cloud2.influxdata.com (Use: InfluxDB UI -> Load Data -> Client Libraries) #define INFLUXDB_URL "REPLACE_WITH_YOUR_DATABASE_URL" // InfluxDB v2 server or cloud API token (Use: InfluxDB UI -> Data -> API Tokens -> Generate API Token) #define INFLUXDB_TOKEN "REPLACE_WITH_YOUR_TOKEN" // InfluxDB v2 organization id (Use: InfluxDB UI -> User -> About -> Common Ids ) #define INFLUXDB_ORG "REPLACE_WITH_YOUR_ORG" // InfluxDB v2 bucket name (Use: InfluxDB UI -> Data -> Buckets) #define INFLUXDB_BUCKET "ESP32" // Set timezone string according to https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html // Examples: // Pacific Time: "PST8PDT" // Eastern: "EST5EDT" // Japanesse: "JST-9" // Central Europe: "CET-1CEST,M3.5.0,M10.5.0/3" #define TZ_INFO "WET0WEST,M3.5.0/1,M10.5.0" // InfluxDB client instance with preconfigured InfluxCloud certificate InfluxDBClient client(INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN, InfluxDbCloud2CACert); // InfluxDB client instance without preconfigured InfluxCloud certificate for insecure connection //InfluxDBClient client(INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN); // Data point Point sensor("wifi_status"); void setup() { Serial.begin(115200); // Setup wifi WiFi.mode(WIFI_STA); wifiMulti.addAP(WIFI_SSID, WIFI_PASSWORD); Serial.print("Connecting to wifi"); while (wifiMulti.run() != WL_CONNECTED) { Serial.print("."); delay(500); } Serial.println(); // Add tags sensor.addTag("device", DEVICE); sensor.addTag("SSID", WiFi.SSID()); // Alternatively, set insecure connection to skip server certificate validation //client.setInsecure(); // Accurate time is necessary for certificate validation and writing in batches // For the fastest time sync find NTP servers in your area: https://www.pool.ntp.org/zone/ // Syncing progress and the time will be printed to Serial. timeSync(TZ_INFO, "pool.ntp.org", "time.nis.gov"); // Check server connection if (client.validateConnection()) { Serial.print("Connected to InfluxDB: "); Serial.println(client.getServerUrl()); } else { Serial.print("InfluxDB connection failed: "); Serial.println(client.getLastErrorMessage()); } } void loop() { // Store measured value into point sensor.clearFields(); // Report RSSI of currently connected network sensor.addField("rssi", WiFi.RSSI()); // Print what are we exactly writing Serial.print("Writing: "); Serial.println(client.pointToLineProtocol(sensor)); // If no Wifi signal, try to reconnect it if (wifiMulti.run() != WL_CONNECTED) { Serial.println("Wifi connection lost"); } // Write point if (!client.writePoint(sensor)) { Serial.print("InfluxDB write failed: "); Serial.println(client.getLastErrorMessage()); } //Wait 10s Serial.println("Wait 10s"); delay(10000); } View raw code Before uploading the code to your board, you need to insert your network credentials, InfludDB URL, organization ID, and bucket name. This example illustrates how to create a data point on the database with tags and fields. It saves the RSSI of the connected network (Wi-Fi strength between the ESP32 and your router) every 10 seconds. Let's take a quick look at how the code works.

How the Code Works

First, it starts by including the required libraries. In this example, it uses the WiFiMulti instead of the WiFi library to connect the ESP32 to a network. It also defines the DEVICE name depending on the selected board. #if defined(ESP32) #include <WiFiMulti.h> WiFiMulti wifiMulti; #define DEVICE "ESP32" #elif defined(ESP8266) #include <ESP8266WiFiMulti.h> ESP8266WiFiMulti wifiMulti; #define DEVICE "ESP8266" #endif Note: the WiFiMulti library allows the ESP32 to connect to the network with the best RSSI (received signal strength indicator) among a list of added networks. In this example, it only connects to one network. Include the required InfluxDB libraries to e able to communicate with InfluxDB: #include <InfluxDbClient.h> #include <InfluxDbCloud.h> Insert your network credentials in the following variables so that the ESP32 can connect to the internet: // WiFi AP SSID #define WIFI_SSID "REPLACE_WITH_YOUR_SSID" // WiFi password #define WIFI_PASSWORD "REPLACE_WITH_YOUR_PASSWORD" Insert the InfluxDB server URL on the following linesthe one you've gotten in this step: // InfluxDB v2 server url, e.g. https://eu-central-1-1.aws.cloud2.influxdata.com (Use: InfluxDB UI -> Load Data -> Client Libraries) #define INFLUXDB_URL "REPLACE_WITH_YOUR_INFLUXDB_URL" Note: if you're running InfluxDB locally on a Raspberry Pi, the URL will be the Raspberry Pi IP address on port 8086. For example 192.168.1.106:8086. Insert your InfluxDB tokensaved in this step: #define INFLUXDB_TOKEN "REPLACE_WITH_YOUR_INFLUXDB_TOKEN" Add your InfluxDB organization namecheck this step. #define INFLUXDB_ORG "REPLACE_WITH_YOUR_INFLUXXDB_ORGANIZATION_ID" Finally, add your InfluxDB bucket name: #define INFLUXDB_BUCKET "ESP32"

Setting your Timezone

You must set your timezone accordingly to these instructions. The easiest way is to check this table and copy your timezone from there. In my case, it's Lisbon timezone: #define TZ_INFO "WET0WEST,M3.5.0/1,M10.5.0"

InfluxDB Client

Now that you have all the required settings, you can create an InfluxDBClient instance. We're creating a secure client that uses a preconfigured certificatelearn more about secure connection here. InfluxDBClient client(INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN, InfluxDbCloud2CACert);

Point

Then, we create a Point instance called sensor. The point will be called wifi_status on the database. Later in the code, we can refer to that point (sensor) to add tags and fields. Point sensor("wifi_status"); Note: A set of data in a database row is known as point. Each point has a measurement, a tag set, a field key, a field value, and a timestamp.

setup()

In the setup(), initialize the Serial Monitor. Serial.begin(115200); Setup and connect to Wi-Fi: // Setup wifi WiFi.mode(WIFI_STA); wifiMulti.addAP(WIFI_SSID, WIFI_PASSWORD); Serial.print("Connecting to wifi"); while (wifiMulti.run() != WL_CONNECTED) { Serial.print("."); delay(500); } Serial.println(); Then, we add tags to our data. Tags are metadata that allows us to better organize our data. It's also an easier way to query data in a more efficient way later on. In this example, we have the device tag that saves the device name (either ESP32 or ESP8266), and the SSID tag that saves the SSID of the connected network. To add a tag we call the addTag() method to the sensor point. We pass as arguments the tag key and value. // Add tags sensor.addTag("device", DEVICE); sensor.addTag("SSID", WiFi.SSID()); Imagine that you have this example running on multiple boards and each board has a unique device tag. Then, it would be easier to query the data relative to a specific device using the device tag. The same for the SSID of the connected network. The following lines sync the time with the NTP servers. timeSync(TZ_INFO, "pool.ntp.org", "time.nis.gov"); The following snippet checks the connection to the InfluxDB server: if (client.validateConnection()) { Serial.print("Connected to InfluxDB: "); Serial.println(client.getServerUrl()); } else { Serial.print("InfluxDB connection failed: "); Serial.println(client.getLastErrorMessage()); }

loop()

In the loop(), we add fields (the actual data) to the point. We start by clearing the point fields: sensor.clearFields(); We add a field to that point, using the addField() method and passing as arguments, the key (rssi) and the actual RSSI value (WiFi.RSSI()). sensor.addField("rssi", WiFi.RSSI()); Print in the Serial Monitor what we're writing to the point: Serial.println(client.pointToLineProtocol(sensor)); Finally, to actually add the point to the database, we use the writePoint() method on the InfluxDBClient object and pass as argument the point we want to add: client.writePoint(sensor). We run the command inside an if statement for debugging purposes. if (!client.writePoint(sensor)) { Serial.print("InfluxDB write failed: "); Serial.println(client.getLastErrorMessage()); } We write new data to the database every 10 seconds. //Wait 10s Serial.println("Wait 10s"); delay(10000);

Demonstration Visualizing Data on InfluxDB

After inserting all the required settings on the code, you can upload it to your ESP32 board. If you get any error during compilation, check the following: Check that you have an ESP32 board selected in Tools > Board. Check your ESP32 boards installation version in Tools > Board > Boards Manager > ESP32. Select version 2.0.1 if you're getting issues with other versions. After uploading the code to your board, open the Serial Monitor at a baud rate of 115200. Press the ESP32 on-board RST button to restart the board. It should print something similar on the Serial Monitor: Now, go to your InfludDB account and go to the Data Explorer by clicking on the corresponding icon. Now, you can visualize your data. Start by selecting the bucket you wantin our case, it's the ESP32. Then, we need to add filters to select our data. Select the wifi_status under the _measurement field, your SSID under the SSID tag (in this case we just have one SSID, but if we add multiple SSIDs, we could filter the data easily because we added the SSID as a tag). Finally, select the field tag (rrsi) and device (ESP32). Finally, click on the SUBMIT button. This will display your data in your chosen format. In the upper left corner, you can select different ways to visualize the data. You can create a dashboard to show multiple data visualizations in different formats (gauges, histograms, single stat, etc.) or different data on the same page.

Creating a Dashboard

Click on the Dashboard icon. Then on Create Dashboard > New dashboard. Add a cell. Make the query to get your data and select the visualization you want. Give a name to the cell, for example, ESP32 RSSI History. You can also click on the Customize button to customize the graph. Finally, click on the icon in the top right corner to add the visualization as a cell to your dashboard. You can add other visualizations to your dashboard. You just need to add a new cell for each visualization. For example, I added a table and a single stat that shows the current SSID. You can move your cells to different positions and organize the dashboard in a way that makes sense for you. You can also customize the way the data is refreshed and how many data points you want to see (up to the past 30 days on the free cloud plan).

Wrapping Up

This was just a quick introduction to InfluxDB with the ESP32. You learned how to create a database bucket and how to create and send points using the ESP32. In this example, we're sending the RSSI. In an IoT application, you can add sensor readings, current consumption, or any other data that makes sense for your IoT and Home Automation projects. Follow the next tutorial to learn how to Send BME280 Sensor Readings to InfluxDB with ESP32/ESP8266 boards. We hope you liked this tutorial and that it helped you get started with InfluxDB. We'll create more tutorials about this subject soon. So, stay tuned!

7 Different Ways to Send Notifications with the ESP32

In this guide, we'll show you seven different ways to send notifications with the ESP32. We'll cover sending SMS, emails, WhatsApp messages, and Telegram messages. We'll show you different options for each notification type. Table of Contents In this tutorial, we'll cover the following notification methods: Email: 1 ESP32: Send emails using an SMTP server 2 ESP32: Send emails with IFTTT 3 ESP32: Send emails using a PHP server Telegram: 4 ESP32: Send Telegram messages (using Telegram API) WhatsApp: 5 ESP32: Send WhatsApp messages (using callmebot API) SMS: 6 ESP32: Send SMS using a modem (SIM800L and SIM7000G) 7 ESP32: Send SMS using Twilio API

Send Emails with the ESP32

There are different methods to send emails with the ESP32. SMTP Server Our preferred method is using an SMTP server. SMTP means Simple Mail Transfer Protocol and it is an internet standard for email transmission. To send emails using an ESP32, you can connect it to an SMTP Server. Check the following tutorial to learn how to send emails with the ESP32 using an SMTP server: ESP32 Send Emails using an SMTP Server We also have a similar tutorial using MicroPython firmware: MicroPython: Send Emails with the ESP32/ESP826 IFTTT (Webhooks and email service) Another alternative method is using IFTTT (If this then that). You can create an applet on IFTTT that will send you an email when you make a request to the webhooks service. If you want to experiment with this method, check the following tutorial: ESP32: Email Notifications using IFTTT PHP Server You can create a PHP server that will send you emails upon an ESP32 request. You can use a cloud server or a local server on a Raspberry Pi, for example. Check the tutorial below: ESP32/ESP8266 Send Email Notification using PHP Script

Send Messages to Telegram with the ESP32

Telegram Messenger is a cloud-based instant messaging and voice-over IP service. You can easily install it on your smartphone (Android and iPhone) or computer (PC, Mac, and Linux). It is free and without any ads. Telegram allows you to create bots that you can interact with. Bots are third-party applications that run inside Telegram. Users can interact with bots by sending them messages, commands, and inline requests. You control your bots using HTTPS requests to Telegram Bot API. So, you just need to make some HTTP requests with the ESP32 to be able to send messages to your Telegram account. The ESP32 can also listen to messages that you send to your bot. So, you can also control your boards by sending messages via Telegram. We have several tutorials showing how to use the Telegram API for different purposes: Telegram: ESP32 Motion Detection with Notifications Telegram: Control ESP32 Outputs Telegram Group: Control ESP32 Outputs ESP32 Door Status Monitor with Telegram Notifications Telegram: ESP32-CAM Take and Send Photo

Send Messages to WhatsApp with the ESP32

WhatsApp Messenger, or simply WhatsApp, is an internationally available American freeware, cross-platform centralized instant messaging and voice-over-IP service owned by Meta Platforms. It allows you to send messages using your phone's internet connection, so you can avoid SMS fees. There are different ways to send messages to WhatsApp using the ESP32. We've experimented with the callmebot API and it worked pretty well. You can check our tutorial that explains how to send messages to WhatsApp with the ESP32: ESP32: Send Messages to WhatsApp We also have an example using MicroPython firmware: MicroPython: Send Messages to WhatsApp with the ESP32/ESP826

Send an SMS with the ESP32

To send SMS with the ESP32, you can connect it to a modem and use a SIM card or use a third-party service. There are several modules for the ESP32 that allow you to connect a SIM card. There are also ESP32 development boards that come with a built-in modem like the SIM800L or SIM7000G. To send SMS with these modules, you need a SIM card with an SMS plan or credit. We have tutorials showing how to send SMS with the ESP32 SIM800L and ESP32 SIM7000G. Check them below: ESP32 SIM800L: Send Text Messages (SMS Alert) with Sensor Readings LILYGO T-SIM7000G ESP32: Send SMS Another alternative is to use a third-party service that will send the SMS for you. You just need to make HTTP requests to their API to use their services. We have a tutorial showing how to send SMS with the ESP32 using Twilio services (these are not free, but they provide you with some credit so that you can experiment with the service). Send SMS with the ESP32 (Twilio)

Wrapping Up

In this article, we've shown you different ways to send notifications with the ESP32. We've covered sending emails, messages to Telegram, messages to WhatsApp, and SMS. Sending notifications with the ESP32 is a very useful feature. For example, to send an alert when motion is detected, when a sensor is above or below a certain threshold value, or to send you messages periodically with sensor values or GPIO states. We hope you've found this compilation useful.

Send SMS with the ESP32 (Twilio)

This guide shows how to send SMS with the ESP32 using an online service called Twilio. With this service, you can send SMS with the ESP32 without needing a GSM module or a dedicated physical SIM card. Using Twilio is not free but you can sign up for a free trial for testing purposes. We'll use a free trial account throughout the tutorial. You can send SMS with the ESP32 without the need to rely on third-party services if you have a GSM module and a dedicated SIM card. We have tutorials showing how to send SMS with the ESP32 using the SIM800L and SIM7000G modules: ESP32 SIM800L: Send Text Messages (SMS Alert) with Sensor Readings LILYGO T-SIM7000G ESP32: Connect to the Internet, Send SMS, and Get GPS Data If you're looking for different types of notifications and alerts with the ESP32, you can check the following tutorials: How to send emails with the ESP32? Send WhatsApp messages with the ESP32 Telegram: ESP32 Motion Detection with Notifications (Arduino IDE)

Introducing Twilio

Twilio provides programmable communication tools for making and receiving phone calls, sending and receiving text messages, and performing other communication functions using its web service APIs. Throughout this tutorial, we'll use their programmable messaging services. Twilio is a paid service, but you can sign-up for a free account for testing purposes. Then, if their services are useful for your specific project, you can always upgrade your account later on. We'll use a free trial account throughout this tutorial.

Creating a Free Trial Account

Go to https://www.twilio.com/ and click on Sign up and start building. Enter your details and get started with a free trial.

Set Up the Free Twilio Trial Account

There are some steps you need to follow to set up your free Twilio trial account. We'll describe the steps you need to follow in the present tutorial. You can also check Twilio's official instructions here.

Verify your personal phone number

When you sign up for your free trial account, you verified your personal phone number. It should be on the list of verified phone numbers on your dashboard: Phone Numbers > Manage > Verified Caller IDs.

Verify other recipient numbers

When using the free trial account, you must verify all numbers that you'll want to send an SMS. When verifying the numbers, you'll get an SMS with a verification code that you need to insert. So, you need to have physical access to those numbers. This is not needed on the paid account. On that previous menu (Phone Numbers > Manage > Verified Caller IDs), click on Add new Called ID to add a new verified number. You'll receive an SMS with a verification code on that number.

Get a Twilio Phone Number

To send messages using Twilio, you'll need to purchase a Twilio phone number. At the time of writing this tutorial, when I signed up for the free account, I got a $15.50 credit, which is enough to get a phone number and test sending some SMS. On your dashboard, on the left sidebar, go to Phone Numbers > Manage > Buy a number. You'll see a list of available numbers. Make sure you select SMS on the number capabilities. You can also select the country. Note: I tried purchasing a number from my country (Portugal), but I needed to fill out a regulatory bundle and submit some documents for review. So I ended up buying a card from the US instead, which didn't require any of that and was ten times cheaper. This works for testing purposes, once you decide to move for a more permanent solution, double-check if that number is the most suitable for your case scenario. Once you've chosen a phone number click on the Buy button.

Programmable Messaging Get Set Up

Now, you have everything set up to start creating a programmable messaging service. On your dashboard, on the left sidebar click on Messaging > Try it out > Get Set Up. Then, click on Start set up. Give a name to the Messaging Service, for example, ESP32 Alerts and click on Create Messaging Service. Then, select the Twilio phone number you created previously and click on Add this number. After that, the programmable messaging service will be all set up. You'll get access to your account information: account SID and Auth token. You'll need them later in the ESP32 code. You can test if everything is working as expected by clicking on Try SMS. You'll see a similar page as shown below. Enter the phone number you want to send the message to (it must be a verified numbersee this previous section), select the messaging service you created previously, and write some body text and click on Send test SMS. After a few seconds, you should receive the test SMS on the selected number. All SMS sent from a Twilio trial account will have the text: Sent from your Twilio trial account. This text doesn't show up on premium accounts.

ESP32: Send SMS using Twilio

Sending SMS using Twilio is very straightforward thanks to its API. You can read the SMS API documentation. You simply make HTTP requests with the ESP32 with the right parameters (accordingly to Twilio's API) to send SMS. You can check this tutorial about HTTP requests with the ESP32. Or you can use a library that takes care of all that, and you just need to insert your Twilio account details and the SMS body text. Throughout this tutorial, we'll use a library called twilio-esp32-client.

Installing the twilio-esp32-client Library

The twilio-esp32-client library can be installed through the Arduino IDE Library Manager. Go to Sketch > Include Library > Manage Libraries. Search for twilio-esp32-client and install the library.

ESP32 Send SMS using Twilio Code

Sending code using Twilio using the twilio-esp32-client library is very straightforward. First, you need to create a Twilio instance, and then you just need to call the send_message() method and pass as arguments your Twilio account details, sender and recipient numbers, and the message body. The following code is an example from the library's examples folder. /********* Rui Santos Complete project details at https://RandomNerdTutorials.com/send-sms-esp32-twilio/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. *********/ // Library example: https://github.com/ademuri/twilio-esp32-client #include "twilio.hpp" // Set these - but DON'T push them to GitHub! static const char *ssid = "REPLACE_WITH_YOUR_SSID"; static const char *password = "REPLACE_WITH_YOUR_PASSWORD"; // Values from Twilio (find them on the dashboard) static const char *account_sid = "REPLACE_WITH_YOUR_ACCOUNT_SID"; static const char *auth_token = "REPLACE_WITH_YOUR_ACCOUNT_AUTH_TOKEN"; // Phone number should start with "+<countrycode>" static const char *from_number = "REPLACE_WITH_TWILIO_NUMBER"; // You choose! // Phone number should start with "+<countrycode>" static const char *to_number = "REPLACE_WITH_RECIPIENT_NUMBER"; static const char *message = "Hello from my ESP32 (via twilio)"; Twilio *twilio; void setup() { Serial.begin(115200); Serial.print("Connecting to WiFi network ;"); Serial.print(ssid); Serial.println("'..."); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { Serial.println("Connecting..."); delay(500); } Serial.println("Connected!"); twilio = new Twilio(account_sid, auth_token); delay(1000); String response; bool success = twilio->send_message(to_number, from_number, message, response); if (success) { Serial.println("Sent message successfully!"); } else { Serial.println(response); } } void loop() { } View raw code

How the Code Works

Start by including the twilio-esp32-client library. #include "twilio.hpp" Insert your network credentials on the following lines: static const char *ssid = "REPLACE_WITH_YOUR_SSID"; static const char *password = "REPLACE_WITH_YOUR_PASSWORD"; Insert your Twilio account details: the account SID and token, and the Twilio phone number. static const char *account_sid = "REPLACE_WITH_YOUR_ACCOUNT_SSID"; static const char *auth_token = "REPLACE_WITH_YOUR_ACCOUNT_AUTH_TOKEN"; // Phone number should start with "+<countrycode>" static const char *from_number = "REPLACE_WITH_TWILIO_PHONE_NUMBER"; Insert the recipient number and the message. // Phone number should start with "+<countrycode>" static const char *to_number = "INSERT_RECIPIENT_NUMBER"; static const char *message = "Hello from my ESP32 (via twilio)"; If you're using a free trial account, the recipient number must be on the list of the Verified caller IDs. Create a Twilio pointer variable called twilio. Twilio *twilio; In the setup(), initialize the Serial Monitor and connect the ESP32 to your local network so that it can get access to the internet and make the HTTP requests to send SMS. Serial.begin(115200); Serial.print("Connecting to WiFi network ;"); Serial.print(ssid); Serial.println("'..."); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { Serial.println("Connecting..."); delay(500); } Serial.println("Connected!"); The following line instantiates a new Twilio instance with the account details: twilio = new Twilio(account_sid, auth_token); Then call the send_message() function that accepts as arguments the recipient number, the sender number, the message, and a variable to hold the response. This function makes an HTTP request in the background with all the necessary parameters to Twilio API to send SMS. String response; bool success = twilio->send_message(to_number, from_number, message, response); if (success) { Serial.println("Sent message successfully!"); } else { Serial.println(response); } } This function will return true if the message is successfully sent or the response of the HTTP request in case it fails. Sending an SMS is not free and it will be deducted from the credit on your Twilio account. So, we're just sending one SMS on the setup() when the board starts. The idea is to apply this sample code to your own project. The loop() is empty. void loop() { }

Demonstration

After inserting all the required details, you can upload the code to your ESP32 board. Select an ESP32 board in Tools > Board and select the COM port in Tools > Port. Then, click on the Upload button. After uploading, open the Serial Monitor at a baud rate of 115200. Press the ESP32 RST button to restart the board. If everything goes as expected, you should receive a similar message as shown below. After a few seconds, you should receive an SMS from Twilio on your phone.

Wrapping Up

In this tutorial, you learned how to send SMS with the ESP32 using Twilio programmable messaging API. The advantage of using this method is that you don't need to have a modem or a physical SIM card to send SMS with your board. However, you need to buy a Twilio phone number, and you'll need to pay a monthly subscription for the card. You'll also need to pay for each SMS you send. You can sign up for a free trial account that gives you credit to experiment with Twilio in your projectsso, you can try their services for a while for free. If you feel their service is the right for your projects, then you can update your account later on. You can also send SMS with the ESP32 using other methodsusing modems like the SIM800L, SIM7000G, and others. We have tutorials showing how to send SMS with the ESP32 using the SIM800L and SIM7000G modules: ESP32 SIM800L: Send Text Messages (SMS Alert) with Sensor Readings LILYGO T-SIM7000G ESP32: Connect to the Internet, Send SMS, and Get GPS Data We hope you find this tutorial useful. Thanks for reading.
SMART HOME with Raspberry Pi ESP32 and ESP8266 Node-RED InfluxDB eBook

SMART HOME with Raspberry Pi, ESP32, ESP8266 [eBook]

Learn how to build a home automation system and we'll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD Learn how to build a home automation system and we'll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD

HTTPS Requests (Arduino IDE)

In this guide, you'll learn how to make HTTPS requests with the ESP32. We'll introduce you to some HTTPS fundamental concepts and provide several examples (with and without certificates) using two different libraries: HttpClient and WiFiClientSecure. Using an ESP8266 board? Check this tutorial instead: ESP8266 NodeMCU HTTPS Requests Table of Contents Throughout this article, we'll cover the following subjects: What is HTTPS? Why do you need HTTPS with the ESP32? SSL/TLS Certificates Certificate Chain Certificates Expiration Date Getting a Server's Certificate using Google Chrome HTTPS Requests with the ESP32 (WiFiClientSecure) ESP32 HTTPS Requests with Certificate ESP32 HTTPS Requests without Certificate HTTPS Requests with the ESP32 (HTTPClient) ESP32 HTTPS Requests with Certificate ESP32 HTTPS Requests without Certificate

Introduction

To understand how to make HTTPS requests with the ESP32, it's better to be familiar with some fundamental concepts that we'll explain next. We also recommend taking a look at the following article: ESP32/ESP8266 with HTTPS and SSL/TLS Encryption: Basic Concepts

What is HTTPS?

HTTPS is the secure version of the HTTP protocol, hence the S, which stands for secure. HTTP is a protocol to transfer data over the internet. When that data is encrypted with SSL/TLS, it's called HTTPS. To simplify, HTTPS is just the HTTP protocol but with encrypted data using SSL/TLS.

Why do you need HTTPS with the ESP32?

Using HTTPS ensures the following: 1) Encryption: all traffic between the ESP32 and a server will be encryptedno one can spy on your requests and passwords, they will only see gibberish. When using the ESP32 libraries to make HTTPS requests, they take care of encryption and decryption of the messages. 2) Server trust (identification): when using HTTPS, via TLS/SSL certificates, you ensure you are connected to the server you would expectthis means, you always know to who you are connected to. To make sure we are connected to the right server, we need to check the server certificate on the ESP32. This means we need to download the server certificate and hard code it on our sketch so that we can check if we're actually connected to the server we are expecting.

TLS/SSL Certificates

SSL certificates are issued by legitimate Certificate Authorities. One of the most known is LetsEncrypt. Certificate Authorities confirm the identity of the certificate owner and provide proof that the certificate is valid. The certificate also contains the server's public key for asymmetrically encrypted communication with a client. When a Certificate Authority issues a certificate, it signs the certificate with its root certificate. This root certificate should be on the database of trusted certificates called a root store. Your browser and the operating system contain a database of root certificates that they can trust (root store). The following screenshot shows some of the trusted root certificates. So, when you connect to a website using your browser, it checks if its certificate was signed by a root certificate that belongs to its root store. New root certificates are added or deleted to the root store with each browser update. When you're using an ESP32, you need to upload the certificates that you trust to your board. Usually, you'll add only the certificate for the server you'll want to connect to. But, it's also possible to upload a root store to your board to have more options, and don't have to worry about searching for a specific website's certificate.

Certificate Chain

An SSL certificate is part of an SSL certificate chain. What is a certificate chain? A certificate chain includes the following: root certificate (from a Certificate Authority); one or more intermediate certificates; the server certificate. The server certificate is what makes your browser show a secure padlock icon when you visit a website. It means the server has a valid SSL/TLS certificate and all the connections with the website are encrypted. A valid SSL/TLS certificate is a certificate trusted by your browser. What makes it trustable? As we've mentioned previously, SSL/TLS certificates are issued by Certificate Authorities. However, these authorities don't issue certificates directly to websites. They use intermediates that will issue the server certificate (Certificate Authority > Intermediate certificate > server certificate). The following screenshot shows an example for the Github website. You can see the certificate hierarchy highlighted with a red rectangle. Your browser checks this certificate chain until it finds the root certificate. If that certificate is in the browser's root store, then it considers the certificate to be valid. In this case, the DigiCert Global Root CA is in the browser's root store. So, it will display the secure icon on the browser bar. The following diagram shows a high-level overview of how it works. In summary: root certificate: it's a self-signed certificate issued by a Certificate Authority. The private key of this certificate is used to sign the next certificate in the hierarchy of certificates. Root certificates are loaded in the trust stores of browsers and operating systems. intermediate certificate: it's signed by the private key of the root certificate. The private key of the intermediate certificate is the one that signs the server certificate. There can be more than one intermediate certificate. server certificate: this certificate is issued to a specific domain name on a server. It's signed by the intermediate certificate private key. If it is valid (trustable certificate chain), the browser displays a secure padlock badge on the search bar next to the website domain. With the ESP32, to check the validity of a server, you can load any of those certificates: root, intermediate, or server certificate.

Certificates Expiration Date

SSL/TLS certificates have an expiry date. You can check on a browser the expiry date of the certificate for a particular server. The server's certificate usually has a short-term validity. So, if you want to use it in your ESP32 projects, you'll need to update your code quite frequently. If you want your code to run for years without worrying, you can use the website's root certificate, which usually has a validity of five to ten years or more.

Getting a Server's Certificate

There are different ways to get the server's certificate. One of the easiest ways is to download the certificate directly from your browser. You can also use OpenSSL and get all the certificate information you need using the command line (we won't cover this method in this tutorial). In this section, you'll learn how to get the server's certificate. We'll generally use the root certificate, but you can use any of the other certificates on the certificate chainyou just need to be aware of the certificate expiry date.

Getting a Server's Certificate using Google Chrome

In this section, we'll show you how to get the certificate for a server using Google Chrome (that's the web browser we use more often). Instructions for other web browsers should be similar. One of the examples we'll use later is to make an HTTPS request to the howmyssl.com website. So, for demonstration purposes, we'll show you how to get its root certificate. It is similar for other websites. How to Get Websites's Certificate using Google Chrome?
    Go to the website that you want to get the certificate for.
    Click on the padlock icon and then click on Show connection details.
    Then, click on Show certificate.
    A new window will open the all the information about the website's certificate. Click on the Details tab, make sure you select the root certificate (that's what we're looking for in this example), then click on Export
    Select a place on your computer to save the certificate. Save it on the default format: Base64-encoded ASCII, single certificate (*.pem, .crt). And that's it.
You can double-click on the certificate to check it's details, including the expiration date. Open the certificate using Notepad or other similar software. You should get something similar as shown below. We need to convert this to Arduino multi-line string, so that we can use it in our sketch. Basically, you need to add a at the beginning of each line and a \n \ at the end of each line, except the last line that you should add \n. So, you'll get something as shown below:

HTTPS Requests with the ESP32

Now that you know all the major important aspects of certificates and how to get a server's certificate, let's finally take a look at how to make HTTPS requests on the ESP32 using the Arduino core. We'll cover different methods using two different libraries: WiFiClientSecure and HTTPClient.

ESP32 HTTPS Requests using WiFiClientSecure Library

You can find a simple example showing how to make HTTPS requests with the WiFiClientSecure library on your Arduino IDE.

ESP32 HTTPS Requests with Certificate

Make sure you have an ESP32 board selected in Tools > Board. Then, go to File > Examples > WiFiClientSecure > WiFiClientSecure. You can modify the following code with the certificate we got from the previous steps, which is valid until 2035. /* Complete project details: https://RandomNerdTutorials.com/esp32-https-requests/ Wifi secure connection example for ESP32 - Running on TLS 1.2 using mbedTLS Suporting the following chipersuites: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384","TLS_DHE_RSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_ECDSA_WITH_AES_256_CCM","TLS_DHE_RSA_WITH_AES_256_CCM","TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384","TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384","TLS_DHE_RSA_WITH_AES_256_CBC_SHA256","TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA","TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA","TLS_DHE_RSA_WITH_AES_256_CBC_SHA","TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8","TLS_DHE_RSA_WITH_AES_256_CCM_8","TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384","TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384","TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256","TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA","TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256","TLS_DHE_RSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_ECDSA_WITH_AES_128_CCM","TLS_DHE_RSA_WITH_AES_128_CCM","TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256","TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256","TLS_DHE_RSA_WITH_AES_128_CBC_SHA256","TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA","TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA","TLS_DHE_RSA_WITH_AES_128_CBC_SHA","TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8","TLS_DHE_RSA_WITH_AES_128_CCM_8","TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA","TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA","TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA","TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA","TLS_DHE_PSK_WITH_AES_256_GCM_SHA384","TLS_DHE_PSK_WITH_AES_256_CCM","TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384","TLS_DHE_PSK_WITH_AES_256_CBC_SHA384","TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA","TLS_DHE_PSK_WITH_AES_256_CBC_SHA","TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384","TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384","TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384","TLS_PSK_DHE_WITH_AES_256_CCM_8","TLS_DHE_PSK_WITH_AES_128_GCM_SHA256","TLS_DHE_PSK_WITH_AES_128_CCM","TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256","TLS_DHE_PSK_WITH_AES_128_CBC_SHA256","TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA","TLS_DHE_PSK_WITH_AES_128_CBC_SHA","TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256","TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256","TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256","TLS_PSK_DHE_WITH_AES_128_CCM_8","TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA","TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA","TLS_RSA_WITH_AES_256_GCM_SHA384","TLS_RSA_WITH_AES_256_CCM","TLS_RSA_WITH_AES_256_CBC_SHA256","TLS_RSA_WITH_AES_256_CBC_SHA","TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384","TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384","TLS_ECDH_RSA_WITH_AES_256_CBC_SHA","TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384","TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384","TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA","TLS_RSA_WITH_AES_256_CCM_8","TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256","TLS_RSA_WITH_CAMELLIA_256_CBC_SHA","TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384","TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384","TLS_RSA_WITH_AES_128_GCM_SHA256","TLS_RSA_WITH_AES_128_CCM","TLS_RSA_WITH_AES_128_CBC_SHA256","TLS_RSA_WITH_AES_128_CBC_SHA","TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256","TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256","TLS_ECDH_RSA_WITH_AES_128_CBC_SHA","TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256","TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256","TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA","TLS_RSA_WITH_AES_128_CCM_8","TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_RSA_WITH_CAMELLIA_128_CBC_SHA","TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_RSA_WITH_3DES_EDE_CBC_SHA","TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA","TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA","TLS_RSA_PSK_WITH_AES_256_GCM_SHA384","TLS_RSA_PSK_WITH_AES_256_CBC_SHA384","TLS_RSA_PSK_WITH_AES_256_CBC_SHA","TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384","TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384","TLS_RSA_PSK_WITH_AES_128_GCM_SHA256","TLS_RSA_PSK_WITH_AES_128_CBC_SHA256","TLS_RSA_PSK_WITH_AES_128_CBC_SHA","TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256","TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256","TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA","TLS_PSK_WITH_AES_256_GCM_SHA384","TLS_PSK_WITH_AES_256_CCM","TLS_PSK_WITH_AES_256_CBC_SHA384","TLS_PSK_WITH_AES_256_CBC_SHA","TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384","TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384","TLS_PSK_WITH_AES_256_CCM_8","TLS_PSK_WITH_AES_128_GCM_SHA256","TLS_PSK_WITH_AES_128_CCM","TLS_PSK_WITH_AES_128_CBC_SHA256","TLS_PSK_WITH_AES_128_CBC_SHA","TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256","TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256","TLS_PSK_WITH_AES_128_CCM_8","TLS_PSK_WITH_3DES_EDE_CBC_SHA","TLS_EMPTY_RENEGOTIATION_INFO_SCSV"] 2017 - Evandro Copercini - Apache 2.0 License. */ #include <WiFiClientSecure.h> const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; const char* server = "www.howsmyssl.com"; // Server URL // www.howsmyssl.com root certificate authority, to verify the server // change it to your server root CA // SHA1 fingerprint is broken now! const char* test_root_ca= \ "-----BEGIN CERTIFICATE-----\n" \ "MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n" \ "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n" \ "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n" \ "WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n" \ "ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n" \ "MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n" \ "h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n" \ "0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n" \ "A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n" \ "T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\n" \ "B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n" \ "B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n" \ "KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n" \ "OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n" \ "jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n" \ "qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n" \ "rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n" \ "HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n" \ "hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n" \ "ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n" \ "3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\n" \ "NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\n" \ "ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\n" \ "TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\n" \ "jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n" \ "oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n" \ "4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n" \ "mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n" \ "emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n" \ "-----END CERTIFICATE-----\n"; // You can use x.509 client certificates if you want //const char* test_client_key = ""; //to verify the client //const char* test_client_cert = ""; //to verify the client WiFiClientSecure client; void setup() { //Initialize serial and wait for port to open: Serial.begin(115200); delay(100); Serial.print("Attempting to connect to SSID: "); Serial.println(ssid); WiFi.begin(ssid, password); // attempt to connect to Wifi network: while (WiFi.status() != WL_CONNECTED) { Serial.print("."); // wait 1 second for re-trying delay(1000); } Serial.print("Connected to "); Serial.println(ssid); client.setCACert(test_root_ca); //client.setCertificate(test_client_cert); // for client verification //client.setPrivateKey(test_client_key);// for client verification Serial.println("\nStarting connection to server..."); if (!client.connect(server, 443)) Serial.println("Connection failed!"); else { Serial.println("Connected to server!"); // Make a HTTP request: client.println("GET https://www.howsmyssl.com/a/check HTTP/1.0"); client.println("Host: www.howsmyssl.com"); client.println("Connection: close"); client.println(); while (client.connected()) { String line = client.readStringUntil('\n'); if (line == "\r") { Serial.println("headers received"); break; } } // if there are incoming bytes available // from the server, read them and print them: while (client.available()) { char c = client.read(); Serial.write(c); } client.stop(); } } void loop() { // do nothing } View raw code This example establishes a secure connection with the www.howsmyssl.com website and checks its certificate to ensure we're connected to the server that we expect. If you're used to making HTTP requests with the ESP32 using the WiFiClient library, this example is not much different.

How does the Code Work?

You need to include the WiFiClientSecure library. #include <WiFiClientSecure.h> Insert your network credentials in the following lines. const char* ssid = "REPLACE_WITH_YOUR_SSID"; // your network SSID (name of wifi network) const char* password = "REPLACE_WITH_YOUR_PASSWORD"; // your network password Insert the server URL. In this case, we'll make a request to www.howsmyssl.com. This website will return how good the SSL of the client is (in this case, the ESP32). const char* server = "www.howsmyssl.com"; // Server URL Then, you need to insert the server certificate. We're using the root certificate. const char* test_root_ca= \ "-----BEGIN CERTIFICATE-----\n" \ "MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n" \ "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n" \ "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n" \ "WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n" \ "ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n" \ "MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n" \ "h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n" \ "0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n" \ "A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n" \ "T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\n" \ "B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n" \ "B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n" \ "KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n" \ "OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n" \ "jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n" \ "qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n" \ "rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n" \ "HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n" \ "hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n" \ "ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n" \ "3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\n" \ "NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\n" \ "ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\n" \ "TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\n" \ "jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n" \ "oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n" \ "4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n" \ "mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n" \ "emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n" \ "-----END CERTIFICATE-----\n"; Create a new client called client using WiFiClient secure. WiFiClientSecure client; In the setup(), initialize the Serial Monitor and connect to your network. //Initialize serial and wait for port to open: Serial.begin(115200); delay(100); Serial.print("Attempting to connect to SSID: "); Serial.println(ssid); WiFi.begin(ssid, password); // attempt to connect to Wifi network: while (WiFi.status() != WL_CONNECTED) { Serial.print("."); // wait 1 second for re-trying delay(1000); } Serial.print("Connected to "); Serial.println(ssid); The following line set the client certificate using the setCACert() method on the client. client.setCACert(test_root_ca); Then, the client connects to the server. For HTTPS, you need to use port 443. if (!client.connect(server, 443)) Serial.println("Connection failed!"); If the connection is successful, we can make the HTTP request. In this case, we're making a GET request. Note that you need to use the https:// before the URL you'll make a request to. else { Serial.println("Connected to server!"); // Make a HTTP request: client.println("GET https://www.howsmyssl.com/a/check HTTP/1.0"); client.println("Host: www.howsmyssl.com"); client.println("Connection: close"); client.println(); Finally, we get and print the response from the server: while (client.connected()) { String line = client.readStringUntil('\n'); if (line == "\r") { Serial.println("headers received"); break; } } // if there are incoming bytes available // from the server, read them and print them: while (client.available()) { char c = client.read(); Serial.write(c); } In the end, we close the connection with the client. client.stop(); In this example, we make the request once in the setup(). The loop() is empty, but you can add any other tasks that you need in your project. Or, depending on the application, you can make the request on the loop(). void loop() { // do nothing } In summary, to make HTTPS requests: Include the WiFiClientSecure library; Create a WiFiClientSecure client; Use port 443; Use the setCACert() function to set the client certificate. Use https on the URL when making the HTTPS request.

Demonstration

Upload the code to your board. Open the Serial Monitor at a baud rate of 115200 and press the onboard RST button. You should get something as shown in the following screenshot. If you scroll to the right, you'll get the result of how secure the connection is. You should get a Probably Okay.

ESP32 HTTPS Requests without Certificate

If you want to skip the SSL server certificate verification, but you still want to have encrypted communication, you can remove the following line: client.setCACert(test_root_ca); And add the following line before connecting with the client: client.setInsecure(); The complete example can be found below. /* Complete project details: https://RandomNerdTutorials.com/esp32-https-requests/ Based on the WiFiClientSecure example HTTPS Requests without Certificate Wifi secure connection example for ESP32 Running on TLS 1.2 using mbedTLS Suporting the following chipersuites: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384","TLS_DHE_RSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_ECDSA_WITH_AES_256_CCM","TLS_DHE_RSA_WITH_AES_256_CCM","TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384","TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384","TLS_DHE_RSA_WITH_AES_256_CBC_SHA256","TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA","TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA","TLS_DHE_RSA_WITH_AES_256_CBC_SHA","TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8","TLS_DHE_RSA_WITH_AES_256_CCM_8","TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384","TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384","TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256","TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA","TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256","TLS_DHE_RSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_ECDSA_WITH_AES_128_CCM","TLS_DHE_RSA_WITH_AES_128_CCM","TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256","TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256","TLS_DHE_RSA_WITH_AES_128_CBC_SHA256","TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA","TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA","TLS_DHE_RSA_WITH_AES_128_CBC_SHA","TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8","TLS_DHE_RSA_WITH_AES_128_CCM_8","TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA","TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA","TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA","TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA","TLS_DHE_PSK_WITH_AES_256_GCM_SHA384","TLS_DHE_PSK_WITH_AES_256_CCM","TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384","TLS_DHE_PSK_WITH_AES_256_CBC_SHA384","TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA","TLS_DHE_PSK_WITH_AES_256_CBC_SHA","TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384","TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384","TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384","TLS_PSK_DHE_WITH_AES_256_CCM_8","TLS_DHE_PSK_WITH_AES_128_GCM_SHA256","TLS_DHE_PSK_WITH_AES_128_CCM","TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256","TLS_DHE_PSK_WITH_AES_128_CBC_SHA256","TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA","TLS_DHE_PSK_WITH_AES_128_CBC_SHA","TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256","TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256","TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256","TLS_PSK_DHE_WITH_AES_128_CCM_8","TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA","TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA","TLS_RSA_WITH_AES_256_GCM_SHA384","TLS_RSA_WITH_AES_256_CCM","TLS_RSA_WITH_AES_256_CBC_SHA256","TLS_RSA_WITH_AES_256_CBC_SHA","TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384","TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384","TLS_ECDH_RSA_WITH_AES_256_CBC_SHA","TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384","TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384","TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA","TLS_RSA_WITH_AES_256_CCM_8","TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256","TLS_RSA_WITH_CAMELLIA_256_CBC_SHA","TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384","TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384","TLS_RSA_WITH_AES_128_GCM_SHA256","TLS_RSA_WITH_AES_128_CCM","TLS_RSA_WITH_AES_128_CBC_SHA256","TLS_RSA_WITH_AES_128_CBC_SHA","TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256","TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256","TLS_ECDH_RSA_WITH_AES_128_CBC_SHA","TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256","TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256","TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA","TLS_RSA_WITH_AES_128_CCM_8","TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_RSA_WITH_CAMELLIA_128_CBC_SHA","TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_RSA_WITH_3DES_EDE_CBC_SHA","TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA","TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA","TLS_RSA_PSK_WITH_AES_256_GCM_SHA384","TLS_RSA_PSK_WITH_AES_256_CBC_SHA384","TLS_RSA_PSK_WITH_AES_256_CBC_SHA","TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384","TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384","TLS_RSA_PSK_WITH_AES_128_GCM_SHA256","TLS_RSA_PSK_WITH_AES_128_CBC_SHA256","TLS_RSA_PSK_WITH_AES_128_CBC_SHA","TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256","TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256","TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA","TLS_PSK_WITH_AES_256_GCM_SHA384","TLS_PSK_WITH_AES_256_CCM","TLS_PSK_WITH_AES_256_CBC_SHA384","TLS_PSK_WITH_AES_256_CBC_SHA","TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384","TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384","TLS_PSK_WITH_AES_256_CCM_8","TLS_PSK_WITH_AES_128_GCM_SHA256","TLS_PSK_WITH_AES_128_CCM","TLS_PSK_WITH_AES_128_CBC_SHA256","TLS_PSK_WITH_AES_128_CBC_SHA","TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256","TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256","TLS_PSK_WITH_AES_128_CCM_8","TLS_PSK_WITH_3DES_EDE_CBC_SHA","TLS_EMPTY_RENEGOTIATION_INFO_SCSV"] 2017 - Evandro Copercini - Apache 2.0 License. */ #include <WiFiClientSecure.h> const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; const char* server = "www.howsmyssl.com"; // Server URL WiFiClientSecure client; void setup() { //Initialize serial and wait for port to open: Serial.begin(115200); delay(100); Serial.print("Attempting to connect to SSID: "); Serial.println(ssid); WiFi.begin(ssid, password); // attempt to connect to Wifi network: while (WiFi.status() != WL_CONNECTED) { Serial.print("."); // wait 1 second for re-trying delay(1000); } Serial.print("Connected to "); Serial.println(ssid); client.setInsecure(); Serial.println("\nStarting connection to server..."); if (!client.connect(server, 443)) Serial.println("Connection failed!"); else { Serial.println("Connected to server!"); // Make a HTTP request: client.println("GET https://www.howsmyssl.com/a/check HTTP/1.0"); client.println("Host: www.howsmyssl.com"); client.println("Connection: close"); client.println(); while (client.connected()) { String line = client.readStringUntil('\n'); if (line == "\r") { Serial.println("headers received"); break; } } // if there are incoming bytes available // from the server, read them and print them: while (client.available()) { char c = client.read(); Serial.write(c); } client.stop(); } } void loop() { // do nothing } View raw code With this example, your connection is still encrypted, but you won't be sure if you're talking to the right server. This scenario is useful for testing purposes.

ESP32 HTTPS Requests with Certificate Bundle

Instead of just using one certificate, you can use a certificate bundle: a collection of trusted certificates that you can load into your board. Then, you don't have to worry about getting the certificate for a specific server. The WiFiClient library provides some information about how to use a certificate bundle on the following link: https://github.com/espressif/arduino-esp32/blob/master/libraries/WiFiClientSecure/README.md#using-a-bundle-of-root-certificate-authority-certificates I followed all the instructions provided, and got the following issue: [ 1799][E][ssl_client.cpp:37] _handle_error(): [start_ssl_client():276]: (-12288) X509 - A fatal error occurred, eg the chain is too long or the vrfy callback failed If anyone knows how to fix this issue, please share in the comments below.

ESP32 HTTP Requests using HTTPClient Library

The HTTPClient library provides a simple example showing how to make HTTPS requests with the ESP32. You can find the example in your Arduino IDE. First, make sure you have an ESP32 board selected in Tools > Board. Then, go to File > Examples > HTTPClient > BasicHttpsClient. We created new sketches based on that example. See the code below.

ESP32 HTTPS Requests with Certificate

The following sketch makes a request to howsmyssl.com like the previous examples but uses the HTTPClient library. It checks the server certificate. We'll use the root certificate we've gotten in previous steps. /* Complete project details: https://RandomNerdTutorials.com/esp32-https-requests/ Based on the BasicHTTPSClient.ino example found at Examples > BasicHttpsClient */ #include <Arduino.h> #include <WiFi.h> #include <WiFiClientSecure.h> #include <HTTPClient.h> // Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; // www.howsmyssl.com root certificate authority, to verify the server // change it to your server root CA const char* rootCACertificate = \ "-----BEGIN CERTIFICATE-----\n" \ "MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n" \ "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n" \ "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n" \ "WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n" \ "ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n" \ "MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n" \ "h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n" \ "0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n" \ "A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n" \ "T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\n" \ "B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n" \ "B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n" \ "KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n" \ "OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n" \ "jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n" \ "qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n" \ "rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n" \ "HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n" \ "hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n" \ "ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n" \ "3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\n" \ "NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\n" \ "ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\n" \ "TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\n" \ "jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n" \ "oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n" \ "4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n" \ "mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n" \ "emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n" \ "-----END CERTIFICATE-----\n"; void setup() { Serial.begin(115200); Serial.println(); // Initialize Wi-Fi WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); Serial.print("Connecting to WiFi .."); while (WiFi.status() != WL_CONNECTED) { Serial.print('.'); delay(1000); } Serial.println(WiFi.localIP()); } void loop() { WiFiClientSecure *client = new WiFiClientSecure; if(client) { // set secure client with certificate client->setCACert(rootCACertificate); //create an HTTPClient instance HTTPClient https; //Initializing an HTTPS communication using the secure client Serial.print("[HTTPS] begin...\n"); if (https.begin(*client, "https://www.howsmyssl.com/a/check")) { // HTTPS Serial.print("[HTTPS] GET...\n"); // start connection and send HTTP header int httpCode = https.GET(); // httpCode will be negative on error if (httpCode > 0) { // HTTP header has been send and Server response header has been handled Serial.printf("[HTTPS] GET... code: %d\n", httpCode); // file found at server if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) { // print server response payload String payload = https.getString(); Serial.println(payload); } } else { Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str()); } https.end(); } } else { Serial.printf("[HTTPS] Unable to connect\n"); } Serial.println(); Serial.println("Waiting 2min before the next round..."); delay(120000); } View raw code

How does the Code Work?

Start by including the required libraries: WiFi.h, WiFiClientSecure.h, and HTTPClient.h. #include <Arduino.h> #include <WiFi.h> #include <WiFiClientSecure.h> #include <HTTPClient.h> Insert your network credentials in the following lines: // Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; Next, you need to add the server certificate. We're using the root certificate for howsmyssl.com (see previous steps). const char* rootCACertificate = \ "-----BEGIN CERTIFICATE-----\n" \ "MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n" \ "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n" \ "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n" \ "WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n" \ "ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n" \ "MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n" \ "h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n" \ "0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n" \ "A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n" \ "T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\n" \ "B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n" \ "B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n" \ "KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n" \ "OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n" \ "jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n" \ "qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n" \ "rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n" \ "HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n" \ "hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n" \ "ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n" \ "3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\n" \ "NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\n" \ "ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\n" \ "TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\n" \ "jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n" \ "oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n" \ "4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n" \ "mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n" \ "emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n" \ "-----END CERTIFICATE-----\n"; In the setup () initialize the Serial Monitor and connect to Wi-Fi. void setup() { Serial.begin(115200); Serial.println(); // Initialize Wi-Fi WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); Serial.print("Connecting to WiFi .."); while (WiFi.status() != WL_CONNECTED) { Serial.print('.'); delay(1000); } Serial.println(WiFi.localIP()); } In the loop(), create a pointer to WiFiClientSecure called client. WiFiClientSecure *client = new WiFiClientSecure; Set a secure client with the certificate using the setCACert() method: client->setCACert(rootCACertificate); Then, create an HTTPClient instance called https. //create an HTTPClient instance HTTPClient https; Initialize the https client on the host specified using the begin() method. In this case, we're making a request on the following URL: https://www.howsmyssl.com/a/check. if (https.begin(*client, "https://www.howsmyssl.com/a/check")) { // HTTPS Get the server response code. int httpCode = https.GET(); If the response code is a positive number, it means the connection was established successfully, so we can read the response payload using the getString() method on the https object. Then, we can print the payload in the Serial Monitor. In a practical application, you can do whatever task you need with the ESP32 depending on the received payload. if (https.begin(client, "https://www.howsmyssl.com/a/check")) { // HTTPS Serial.print("[HTTPS] GET...\n"); // start connection and send HTTP header int httpCode = https.GET(); // httpCode will be negative on error if (httpCode > 0) { // HTTP header has been send and Server response header has been handled Serial.printf("[HTTPS] GET... code: %d\n", httpCode); // file found at server if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) { // print server response payload String payload = https.getString(); Serial.println(payload); } } If the response code is a negative number, it means we have an error. We'll print the error code. else { Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str()); } Finally, close the HTTPS connection using the end() method: https.end(); This specific example makes a request every two minutes. You can change it depending on your project requirements. Serial.println("Waiting 2min before the next round..."); delay(120000);

Demonstration

You can change the debug level to get more information about what's going on in the process. Go to Tools > Core Debug Level > Debug. Then, you can upload the code to the ESP32. After uploading the code, open the Serial Monitor at a baud rate of 115200. Press the on-board RST board to start running the newly uploaded code. You should get something similar as shown in the picture below. If you scroll to the right, you'll get the result of how secure the connection is. You should get a Probably Okay.

ESP32 HTTPS Requests without Certificate

If you want to skip the SSL server certificate verification, but you still want to have encrypted communication, you can remove the following line: client.setCACert(test_root_ca); And add the following line before starting the HTTP client: client.setInsecure(); The complete example can be found below. /* Complete project details: https://RandomNerdTutorials.com/esp32-https-requests/ Based on the BasicHTTPSClient.ino example found at Examples > BasicHttpsClient */ #include <Arduino.h> #include <WiFi.h> #include <WiFiClientSecure.h> #include <HTTPClient.h> // Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; void setup() { Serial.begin(115200); Serial.println(); // Initialize Wi-Fi WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); Serial.print("Connecting to WiFi .."); while (WiFi.status() != WL_CONNECTED) { Serial.print('.'); delay(1000); } Serial.println(WiFi.localIP()); } void loop() { WiFiClientSecure *client = new WiFiClientSecure; if(client) { // set secure client without certificate client->setInsecure(); //create an HTTPClient instance HTTPClient https; //Initializing an HTTPS communication using the secure client Serial.print("[HTTPS] begin...\n"); if (https.begin(*client, "https://www.howsmyssl.com/a/check")) { // HTTPS Serial.print("[HTTPS] GET...\n"); // start connection and send HTTP header int httpCode = https.GET(); // httpCode will be negative on error if (httpCode > 0) { // HTTP header has been send and Server response header has been handled Serial.printf("[HTTPS] GET... code: %d\n", httpCode); // file found at server if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) { // print server response payload String payload = https.getString(); Serial.println(payload); } } else { Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str()); } https.end(); } } else { Serial.printf("[HTTPS] Unable to connect\n"); } Serial.println(); Serial.println("Waiting 2min before the next round..."); delay(120000); } View raw code With this example, your connection is still encrypted, but you won't be sure if you're talking to the right server. This scenario is useful for testing purposes. After uploading this example, here's what you should get: Your connection is still encrypted, but it will skip SSL verification.

Wrapping Up

In this tutorial, you learned how to make HTTPS requests with the ESP32. You also learned about the basic concepts of HTTPS protocol and about SSL/TLS certificates. We've taken a look at examples with the WiFiClientSecure and HTTPClient libraries. The examples presented are as simple as possible so that you can modify them and apply them to your own projects. You learned how to make HTTPS requests with and without verification of the SSL/TLS certificate.

with HTTPS and SSL/TLS Encryption: Basic Concepts

This article is a quick and simple introduction to HTTPS and SSL/TLS encryption with the ESP32 and ESP8266 NodeMCU board. We'll take a look at some concepts and terms that you've probably heard before but you might not know exactly what they mean: HTTPS, SSL/TLS, certificates, asymmetric and symmetric key encryption, and more. Table of Contents Throughout this article, we'll cover the following subjects: What is HTTPS?Why do you need HTTPS? What is SSL/TLS? How does SSL/TLS encryption work?Public key and private key Communication over HTTPS SSL certificates Self-signed certificates ESP32: HTTPS requests (Arduino IDE) ESP32 HTTPS server (Arduino IDE) ESP8266: HTTPS requests (Arduino IDE) ESP8266 HTTPS server (Arduino IDE)

What is HTTPS?

HTTPS is the secure version of the HTTP protocol, hence the S, which stands for secure. HTTP is a protocol to transfer data over the internet. When that data is encrypted with SSL/TLS, it's called HTTPS. To simplify, HTTPS is just the HTTP protocol but with encrypted data using SSL/TLS.

Why do you need HTTPS?

Using HTTPS ensures the following: Privacy: no one can spy on your requests and passwords because the messages are encrypted. Integrity: the message is not manipulated on its way to its destination (prevents men-in-the-middle) attacks. Identification: when using HTTPS, via SSL certificates, you ensure you are connected to the server you would expect.

What is SSL/TLS?

SSL stands for Secure Socket Layer and TLS stands for Transport Layer Security. These are two protocols used for secured encryption. SSL is currently deprecated. TLS 1.3 is currently the most recent protocol used for secure encryption on the web.

How does SSL/TLS encryption work?

There are two types of encryption algorithms: symmetric key algorithm and asymmetric key algorithm. Symmetric Key Encryption With a symmetric-key algorithm, the same key is used to encrypt and decrypt the messages. So, both the client and server need to have the same key. The disadvantage of using a symmetric key algorithm is that keys are hard to share and you need to be careful how and with who you distribute the key. Asymmetric Key Encryption The SSL/TLS encryption uses asymmetric keys. How does asymmetric key encryption work? Very briefly: You have two asymmetric keys: a public key and a private key. The public key and private key work together. The public key, as the name suggests, is visible to anyone. Only the private key can decrypt the message encrypted with the corresponding public key.

Public Key and Private Key

In summary, here's how it works: The browser client tries to contact the server. The server sends the public key to the client (browser) via the server's SSL certificate. The browser sends a message to the server encrypted with the public key. Only the ones with the private key (the server) can decipher the message.

Communication over HTTPS

How the communication between the server and client works over HTTPS? The following diagram shows a high-level overview of how it works. You, the client on your browser, try to connect with the server (1); The server sends back its certificate (2) so that the browser can check the authenticity of the server (3). The certificate contains the public key. If the certificate is valid, the client creates a new key (called session key) (4) that will be used later to encrypt communication between the client and server. The client encrypts the session key using the public key sent by the server (5). The server receives the session key encrypted with the public key and can decipher the message because only the server has access to the corresponding private key to decrypt the message (6); From now on, both the client and server have a secret key (that's only known to them) that they can use to encrypt further communication (7) (symmetric key encryption).

SSL Certificates

SSL certificates are issued by legitimate Certificate Authorities. One of the most known is LetsEncrypt. Certificate Authorities also confirm the identity of the certificate owner and provide proof that the certificate is valid. When a Certificate Authority issues a certificate, it signs the certificate with its root certificate. This root certificate should be on the database of trusted certificates. Your browser then checks if the certificate is valid (if it was signed with a root certificate on the database of trusted root certificates) and displays a green lock icon on the browser bar if it is.

Self-signed Certificates

You can self-sign your certificates. These provide the same level of encryption as one generated by an authority, and these are free. However, all browsers will check if the certificate is issued by a trusted Certificate Authority. So, you'll be warned by your browser that the site you're visiting is not safe because it doesn't trust the certificate and so, can't identify its owner. The web browser will display a warning sign and the HTTPS letters in red. This means the website has a certificate, but the certificate is unverified (like self-signed certificates) or out of date. This means that the connection between you and the server is encrypted, but no one can guarantee that the domain really belongs to the company indicated on the site. Self-signed certificates are fine to use on your DIY and IoT projects, intranets, like your local network, or inside a company's network. However, if you're creating a project for a company that will be accessed by clients outside the company network, like a public website, it's best to use a certificate from a Certificate Authority. SSL certificates have an expiry date. So, if you're using an ESP32 to connect to a website via HTTPS, you should keep in mind that you'll need to update the code with the new website's certificate in the future. If you're still confused about all of these new terms, we recommend taking a look at the following website that explains in a fun way how everything works: https://howhttps.works/.

ESP32: HTTPS Requests (Arduino IDE)

If you're familiar with HTTP requests with the ESP32 migrating to HTTPS is very straightforward. If you're using the WiFiClient library, you just need to make the following changes:
    Use WiFiClientSecure.h library instead of WiFiClient.h Use port 443 instead of port 80 Change the host URL to https instead of http
With this, you ensure that your communication is encrypted using TLS. An additional security step is to check the server certificate (the certificate of the website you want to connect to). You can skip this step while testing and prototyping. The communication will be encrypted, but you won't be sure of the integrity of the server you are trying to communicate with. You can also find examples using HTTPS with the HTTPClient library. If you want to start working on your HTTPS requests right away, take a look at the examples provided in the ESP32 package for the Arduino core. WiFiClientSecure example: File > Examples > ESP32 > WFiClientSecure > WiFiClientSecure HTTPClient with HTTPS example: File > Examples > ESP32 > BasicHttpsClient > BasicHttpsClient

ESP32 HTTPS Server (Arduino IDE)

At the moment, there are not many examples of building an HTTPS web server with the ESP32 using the Arduino core. Unfortunately, the AsyncWebServer library that we use in most of our projects, doesn't fully support HTTPS at the moment. Nevertheless, there is another library that provides easy methods to build an ESP32 HTTPS web server, including an example that generates certificates on the fly. Here's a link to the library: esp32_https_server library. If you're familiar with ESP-IDF, you can take a look at the documentation on the following link: ESP-IDF HTTPS Server Documentation

ESP8266 HTTPS Requests (Arduino IDE)

There are several examples that show how to make HTTPS requests with the ESP8266. You can check the examples available in your Arduino IDE. Make sure you have the latest version of the ESP8266 boards installed to make sure you have access to the latest version of the examples and that these will work. To update the ESP8266 boards' installation, you just need to go to Tools > Boards > Boards Manager, search for ESP8266, and install the latest version. Then, you'll have access to the examples' latest version. You can check the following examples: Basic HTTPS Client using the ESP8266HTTPClient library: File > Examples > ESP8266HTTPClient > BasicHttpsClient Basic HTTPS Client using WiFiClientSecure library: File > Examples > ESP8266WiFi > HTTPSRequest You'll need to update the certificates and fingerprints to make the examples work. If you can't make the examples work, don't worry, we'll publish some tutorials with examples and instructions soon.

ESP8266 HTTPS Server (Arduino IDE)

The ESP8266 is not optimized for SSL cryptography, so running an HTTPS Server on the ESP8266 is very demanding. You need to set the clock frequency to 160MHz and even so, you might get unexpected resets on the board. For an ESP8266 HTTPS web server, you can take a look at an example using the ESP8266WebServer library on the following link: ESP8266 HTTPS Server (BearSSL)

Wrapping Up

In this tutorial, we've taken a look at the HTTPS protocol, SSL/TLS encryption, and SSL certificates. I'm far from being an expert in these subjects, so if anything doesn't sound right in this article, please let me know in the comments below. We've also taken a quick look at possible ways to secure your ESP32/ESP8266 IoT projects: how to make HTTPS requests and how to set the ESP32/ESP8266 as an HTTPS server with a certificate. We'll create more tutorials with practical examples about these subjects in the upcoming weeks, so stay tuned. If you have any examples of HTTPS servers with the ESP32 or are familiar with any other libraries to build an HTTPS server, please share them in the comments below. Thanks for reading.
Build-Web-Servers-with-ESP32-and-ESP8266-eBook-2nd-Edition-500px-h

[eBook] Build Web Servers with ESP32 and ESP8266 (2nd Edition)

Build Web Server projects with the ESP32 and ESP8266 boards to control outputs and monitor sensors remotely. Learn HTML, CSS, JavaScript and client-server communication protocols DOWNLOAD Build Web Server projects with the ESP32 and ESP8266 boards to control outputs and monitor sensors remotely. Learn HTML, CSS, JavaScript and client-server communication protocols DOWNLOAD

Send Messages to WhatsApp

In this guide, you'll learn how to send messages to your WhatsApp account with the ESP32. This can be useful to receive notifications from the ESP32 with sensor readings, alert messages when a sensor reading is above or below a certain threshold, when motion is detected, and many other applications. We'll program the ESP32 using Arduino IDE and to send the messages we'll use a free API called CallMeBot. We have a similar tutorial for the ESP8266 board: ESP8266 NodeMCU: Send Messages to WhatsApp

Introducing WhatsApp

WhatsApp Messenger, or simply WhatsApp, is an internationally available American freeware, cross-platform centralized instant messaging and voice-over-IP service owned by Meta Platforms. It allows you to send messages using your phone's internet connection, so you can avoid SMS fees. Build a Home Automation System from Scratch With Raspberry Pi, ESP8266, Arduino, and Node-RED. Home Automation using ESP8266 eBook and video course Build IoT and home automation projects. Arduino Step-by-Step Projects Build 25 Arduino projects with our course, even with no prior experience!

Getting Started with the ESP32 Development Board

New to ESP32? Start here! The ESP32 is a series of low-cost and low-power System on a Chip (SoC) microcontrollers developed by Espressif that include Wi-Fi and Bluetooth wireless capabilities and dual-core processor. If you're familiar with the ESP8266, the ESP32 is its successor, loaded with lots of new features. Updated 29 September 2023 New to the ESP32? You're in the right place. This guide contains all the information you need to get started with the ESP32. Learn what is an ESP32, how to select an ESP32 board, how to get your first program working, and much more. Here's what we'll cover in this guide: Table of Contents Introducing the ESP32 ESP32 Specifications ESP32 vs ESP8266 ESP32 Development Boards How to choose an ESP32 development board? What is the best ESP32 development board for beginners? ESP32 DEVKIT DOIT ESP32 GPIOs Pinout Guide How to program the ESP32? ESP32 with Arduino IDE Upload Code to the ESP32 using Arduino IDE

Introducing the ESP32

First, to get started, what is an ESP32? The ESP32 is a series of chip microcontrollers developed by Espressif. Why are they so popular? Mainly because of the following features: Low-cost: you can get an ESP32 starting at $6, which makes it easily accessible to the general public; Low-power: the ESP32 consumes very little power compared with other microcontrollers, and it supports low-power mode states like deep sleep to save power; Wi-Fi capabilities: the ESP32 can easily connect to a Wi-Fi network to connect to the internet (station mode), or create its own Wi-Fi wireless network (access point mode) so other devices can connect to itthis is essential for IoT and Home Automation projectsyou can have multiple devices communicating with each other using their Wi-Fi capabilities; Bluetooth: the ESP32 supports Bluetooth classic and Bluetooth Low Energy (BLE)which is useful for a wide variety of IoT applications; Dual-core: most ESP32 are dual-core they come with 2 Xtensa 32-bit LX6 microprocessors: core 0 and core 1. Rich peripheral input/output interfacethe ESP32 supports a wide variety of input (read data from the outside world) and output (to send commands/signals to the outside world) peripherals like capacitive touch, ADCs, DACs, UART, SPI, I2C, PWM, and much more. Compatible with the Arduino programming language: those that are already familiar with programming the Arduino board, you'll be happy to know that they can program the ESP32 in the Arduino style. Compatible with MicroPython: you can program the ESP32 with MicroPython firmware, which is a re-implementation of Python 3 targeted for microcontrollers and embedded systems.

ESP32 Specifications

If you want to get a bit more technical and specific, you can take a look at the following detailed specifications of the ESP32 (source: http://esp32.net/)for more details, check the datasheet):
ESP32 module: ESP-WROOM-32
Wireless connectivityWiFi: 150.0 Mbps data rate with HT40 Bluetooth: BLE (Bluetooth Low Energy) and Bluetooth Classic Processor: Tensilica Xtensa Dual-Core 32-bit LX6 microprocessor, running at 160 or 240 MHz Memory: ROM: 448 KB (for booting and core functions) SRAM: 520 KB (for data and instructions) RTC fast SRAM: 8 KB (for data storage and main CPU during RTC Boot from the deep-sleep mode) RTC slow SRAM: 8KB (for co-processor accessing during deep-sleep mode) eFuse: 1 Kbit (of which 256 bits are used for the system (MAC address and chip configuration) and the remaining 768 bits are reserved for customer applications, including Flash-Encryption and Chip-ID) Embedded flash: flash connected internally via IO16, IO17, SD_CMD, SD_CLK, SD_DATA_0 and SD_DATA_1 on ESP32-D2WD and ESP32-PICO-D4. 0 MiB (ESP32-D0WDQ6, ESP32-D0WD, and ESP32-S0WD chips) 2 MiB (ESP32-D2WD chip) 4 MiB (ESP32-PICO-D4 SiP module) Low Power: ensures that you can still use ADC conversions, for example, during deep sleep. Peripheral Input/Output: peripheral interface with DMA that includes capacitive touch ADCs (Analog-to-Digital Converter) DACs (Digital-to-Analog Converter) I2C (Inter-Integrated Circuit) UART (Universal Asynchronous Receiver/Transmitter) SPI (Serial Peripheral Interface) I2S (Integrated Interchip Sound) RMII (Reduced Media-Independent Interface) PWM (Pulse-Width Modulation) Security: hardware accelerators for AES and SSL/TLS

Main Differences Between ESP32 and ESP8266

Previously, we mentioned that the ESP32 is the ESP8266 successor. What are the main differences between ESP32 and ESP8266 boards? The ESP32 adds an extra CPU core, faster Wi-Fi, more GPIOs, and supports Bluetooth 4.2 and Bluetooth low energy. Additionally, the ESP32 comes with touch-sensitive pins that can be used to wake up the ESP32 from deep sleep, and built-in hall effect sensor. Both boards are cheap, but the ESP32 costs slightly more. While the ESP32 can cost around $6 to $12, the ESP8266 can cost $4 to $6 (but it really depends on where you get them and what model you're buying). So, in summary: The ESP32 is faster than the ESP8266; The ESP32 comes with more GPIOs with multiple functions; The ESP32 supports analog measurements on 18 channels (analog-enabled pins) versus just one 10-bit ADC pin on the ESP8266; The ESP32 supports Bluetooth while the ESP8266 doesn't; The ESP32 is dual-core (most models), and the ESP8266 is single core; The ESP32 is a bit more expensive than the ESP8266. For a more detailed analysis of the differences between those boards, we recommend reading the following article: ESP32 vs ESP8266 Pros and Cons.

ESP32 Development Boards

ESP32 refers to the bare ESP32 chip. However, the ESP32 term is also used to refer to ESP32 development boards. Using ESP32 bare chips is not easy or practical, especially when learning, testing, and prototyping. Most of the time, you'll want to use an ESP32 development board. These development boards come with all the needed circuitry to power and program the chip, connect it to your computer, pins to connect peripherals, built-in power and control LEDs, an antenna for wi-fi signal, and other useful features. Others even come with extra hardware like specific sensors or modules, displays, or a camera in the case of the ESP32-CAM.

How to Choose an ESP32 Development Board?

Once you start searching for ESP32 boards online, you'll find there is a wide variety of boards from different vendors. While they all work in a similar way, some boards may be more suitable for some projects than others. When looking for an ESP32 development board there are several aspects you need to take into account: USB-to-UART interface and voltage regulator circuit. Most full-featured development boards have these two features. This is important to easily connect the ESP32 to your computer to upload code and apply power. BOOT and RESET/EN buttons to put the board in flashing mode or reset (restart) the board. Some boards don't have the BOOT button. Usually, these boards go into flashing mode automatically. Pin configuration and the number of pins. To properly use the ESP32 in your projects, you need to have access to the board pinout (like a map that shows which pin corresponds to which GPIO and its features). So make sure you have access to the pinout of the board you're getting. Otherwise, you may end up using the ESP32 incorrectly. Antenna connector. Most boards come with an onboard antenna for Wi-Fi signal. Some boards come with an antenna connector to optionally connect an external antenna. Adding an external antenna increases your Wi-Fi range. Battery connector. If you want to power your ESP32 using batteries, there are development boards that come with connectors for LiPo batteriesthis can be handier. You can also power a regular ESP32 with batteries through the power pins. Extra hardware features. There are ESP32 development boards with extra hardware features. For example, some may come with a built-in OLED display, a LoRa module, a SIM800 module (for GSM and GPRS), a battery holder, a camera, or others.

What is the best ESP32 development board for beginners?

For beginners, we recommend an ESP32 board with a vast selection of available GPIOs, and without any extra hardware features. It's also important that it comes with voltage regular and USB input for power and upload code. In most of our ESP32 projects, we use the ESP32 DEVKIT DOIT board, and that's the one we recommend for beginners. There are different versions of this board with a different number of available pins (30, 36, and 38)all boards work in a similar way. Where to Buy? You can check the following link to find the ESP32 DEVKIT DOIT board in different stores: ESP32 DEVKIT DOIT board Other similar boards with the features mentioned previously may also be a good option like the Adafruit ESP32 Feather, Sparkfun ESP32 Thing, NodeMCU-32S, Wemos LoLin32, etc.

ESP32 DEVKIT DOIT

In this article, we'll be using the ESP32 DEVKIT DOIT board as a reference. If you have a different board, don't worry. The information on this page is also compatible with other ESP32 development boards. The picture below shows the ESP32 DEVKIT DOIT V1 board, version with 36 GPIO pins.

Specifications ESP32 DEVKIT V1 DOIT

The following table shows a summary of the ESP32 DEVKIT V1 DOIT board features and specifications:
Number of cores 2 (dual core)
Wi-Fi 2.4 GHz up to 150 Mbits/s
Bluetooth BLE (Bluetooth Low Energy) and legacy Bluetooth
Architecture 32 bits
Clock frequency Up to 240 MHz
RAM 512 KB
Pins 30, 36, or 38 (depending on the model)
Peripherals Capacitive touch, ADC (analog to digital converter), DAC (digital to analog converter), I2C (Inter-Integrated Circuit), UART (universal asynchronous receiver/transmitter), CAN 2.0 (Controller Area Netwokr), SPI (Serial Peripheral Interface), I2S (Integrated Inter-IC Sound), RMII (Reduced Media-Independent Interface), PWM (pulse width modulation), and more.
Built-in buttons RESET and BOOT buttons
Built-in LEDs built-in blue LED connected to GPIO2; built-in red LED that shows the board is being powered
USB to UART bridge CP2102
This particular ESP32 board comes with 36 pins, 18 on each side. The number of available GPIOs depends on your board model. To learn more about the ESP32 GPIOs, read our GPIO reference guide: ESP32 Pinout Reference: Which GPIO pins should you use? It comes with a microUSB interface that you can use to connect the board to your computer to upload code or apply power. It uses the CP2102 chip (USB to UART) to communicate with your computer via a COM port using a serial interface. Another popular chip is the CH340. Check what's the USB to UART chip converter on your board because you'll need to install the required drivers so that your computer can communicate with the board (more information about this later in this guide). This board also comes with a RESET button (may be labeled EN) to restart the board and a BOOT button to put the board in flashing mode (available to receive code). Note that some boards may not have a BOOT button. It also comes with a built-in blue LED that is internally connected to GPIO 2. This LED is useful for debugging to give some sort of visual physical output. There's also a red LED that lights up when you provide power to the board.

ESP32 GPIOs Pinout Guide

The ESP32 chip comes with 48 pins with multiple functions. Not all pins are exposed in all ESP32 development boards, and some pins should not be used. The ESP32 DEVKIT V1 DOIT board usually comes with 36 exposed GPIOs that you can use to connect peripherals. Power Pins Usually, all boards come with power pins: 3V3, GND, and VIN. You can use these pins to power the board (if you're not providing power through the USB port), or to get power for other peripherals (if you're powering the board using the USB port). General Purpose Input Output Pins (GPIOS) Almost all GPIOs have a number assigned and that's how you should refer to themby their number. With the ESP32 you can decide which pins are UART, I2C, or SPI you just need to set that on the code. This is possible due to the ESP32 chip's multiplexing feature that allows to assign multiple functions to the same pin. If you don't set them on the code, the pins will be configured by default as shown in the figure below (the pin location can change depending on the manufacturer). Additionally, there are pins with specific features that make them suitable or not for a particular project. We have a detailed guide dedicated to the ESP32 GPIOs that we recommend you read: ESP32 Pinout Reference Guide. It shows how to use the ESP32 GPIOs and explains what are the best GPIOs to use depending on your project. The placement of the GPIOs might be different depending on your board model. However, usually, each specific GPIO works in the same way regardless of the development board you're using (with some exceptions). For example, regardless of the board, usually GPIO5 is always the VSPI CS0 pin, GPIO 23 always corresponds to VSPI MOSI for SPI communication, etc.

How to Program the ESP32?

The ESP32 can be programmed using different firmware and programming languages. You can use: Arduino C/C++ using the Arduino core for the ESP32 Espressif IDF (IoT Development Framework) Micropython JavaScript LUA Our preferred method to program the ESP32 is with C/C++ Arduino programming language. We also have some guides and tutorials using MicroPython firmware. Throughout this guide, we'll cover programming the ESP32 using the Arduino core for the ESP32 board. If you prefer using MicroPython, please refer to this guide: Getting Started with MicroPython on ESP32.

Programming ESP32 with Arduino IDE

To program your boards, you need an IDE to write your code. For beginners, we recommend using Arduino IDE. While it's not the best IDE, it works well and is simple and intuitive to use for beginners. After getting familiar with Arduino IDE and you start creating more complex projects, you may find it useful to use VS Code with the Platformio extension instead. If you're just getting started with the ESP32, start with Arduino IDE. At the time of writing this tutorial, we recommend using the legacy version (1.8.19) with the ESP32. While version 2 works well with Arduino, there are still some bugs and some features that are not supported yet for the ESP32.

Installing Arduino IDE

To run Arduino IDE, you need JAVA installed on your computer. If you don't, go to the following website to download and install the latest version: http://java.com/download.

Downloading Arduino IDE

To download the Arduino IDE, visit the following URL: https://www.arduino.cc/en/Main/Software Don't install the 2.0 version. At the time of writing this tutorial, we recommend using the legacy version (1.8.19) with the ESP32. While version 2 works well with Arduino, there are still some bugs and some features that are not supported yet for the ESP32. Scroll down until you find the legacy version section. Select your operating system and download the software. For Windows, we recommend downloading the Windows ZIP file.

Running Arduino IDE

Grab the folder you've just downloaded and unzip it. Run the executable file called arduino.exe (highlighted below). The Arduino IDE window should open.

Installing the ESP32 in Arduino IDE

To be able to program the ESP32 using Arduino IDE, you need to add support for the ESP32 boards. Follow the next steps:
    Go to File > Preferences.
    Enter the following into the Additional Board Manager URLs field. This will add support for ESP32 and ESP8266 boards as well.
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json, http://arduino.esp8266.com/stable/package_esp8266com_index.json See the figure below. Then, click the OK button.
    Open the Boards Manager. Go to Tools > Board >Boards Manager
    Search for ESP32 and install the ESP32 by Espressif Systems:
That's it. It will be installed after a few seconds. After this, restart your Arduino IDE. Then, go to Tools > Board and check that you have ESP32 boards available. Now, you're ready to start programming your ESP32 using Arduino IDE.

ESP32 Examples

In your Arduino IDE, you can find multiple examples for the ESP32. First, make sure you have an ESP32 board selected in Tools > Board. Then, simply go to File > Examples and check out the examples under the ESP32 section.

Update the ESP32 Core in Arduino IDE

Once in a while, it's a good idea to check if you have the latest version of the ESP32 boards add-on installed. You just need to go to Tools > Board > Boards Manager, search for ESP32, and check the version that you have installed. If there is a more recent version available, select that version to install it.

Upload Code to the ESP32 using Arduino IDE

To show you how to upload code to your ESP32 board, we'll try a simple example available in the Arduino IDE examples for the ESP32. First, make sure you have an ESP32 selected in Tools > Board. Then, go to File > Examples > WiFi > WiFiScan. This will load a sketch that scans Wi-Fi networks within the range of your ESP32 board. Connect your ESP32 development board to your computer using a USB cable. If you have an ESP32 DEVKIT DOIT board, the built-in red LED will turn on. This indicates the board is receiving power. Important: you must use a USB cable with data wires. Some USB cables from chargers or power banks are power only and they don't transfer datathese won't work. Now, follow the next steps to upload the code. 1) Go to Tools > Board, scroll down to the ESP32 section and select the name of your ESP32 board. In my case, it's the DOIT ESP32 DEVKIT V1 board. 2) Go to Tools > Port and select a COM port available. If the COM port is grayed out, this means you don't have the required USB drivers. Check the section Installing USB Drivers before proceeding. 3) Press the upload button. Some boards will automatically go into flashing mode and the code will be successfully uploaded straight away. Other boards don't go into flashing mode automatically, so you may end up getting the following error. Failed to connect to ESP32: Timed out... Connecting... Or something like: A fatal error occurred: Failed to connect to ESP32: Wrong boot mode detected (0x13)! The chip needs to be in download mode. This means the ESP32 was not in flashing mode when you tried to upload the code. In this situation, you should long press the board BOOT button, when you start seeing the Connecting. message on the debugging window. Note: in some boards, a simple trick can make the ESP32 go into flashing mode automatically. Check it out on the following tutorial: [SOLVED] Failed to connect to ESP32: Timed out waiting for packet header. Now, the code should be successfully uploaded to the board. You should get a Done uploading message.

Demonstration

To see if the code is working as expected, open the Serial Monitor at a baud rate of 115200. Press the ESP32 RST or EN button to restart the board and start running the newly uploaded code. You should get a list of nearby wi-fi networks. This means everything went as expected.

Installing ESP32 USB Drivers

After connecting the ESP32 board to your computer, if the COM port in Arduino IDE is grayed out, it means you don't have the necessary USB drivers installed on your computer. Most ESP32 boards either use the CP2101 or CH340 drivers. Check the USB to UART converter on your board, and install the corresponding drivers. You'll easily find instructions with a quick google search. For example install CP2101 drivers Windows.

Wrapping Up

We hope you've found this getting started guide useful. I think we've included all the required information for you to get started. You learned what is an ESP32, how to choose an ESP32 development board, and how to upload new code to the ESP32 using Arduino IDE. Want to learn more? We recommend the following tutorials to get started: ESP32 Digital Inputs and Digital Outputs (Arduino IDE) ESP32 Web Server Tutorial Also, don't forget to take a look at the ESP32 pinout to learn how to use its GPIOs: ESP32 Pinout Reference: Which GPIO pins should you use? If you're serious about learning about the ESP32, we recommend taking a look at our best-selling eBook: Learn ESP32 with Arduino IDE eBook You can also check all our free ESP32 tutorials and guides on the following link: More ESP32 Projects If you like ESP32 make sure you subscribe to our blog, so you don't miss upcoming projects. Do you have any questions? Leave a comment down below! Thanks for reading.
SMART HOME with Raspberry Pi ESP32 and ESP8266 Node-RED InfluxDB eBook

SMART HOME with Raspberry Pi, ESP32, ESP8266 [eBook]

Learn how to build a home automation system and we'll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD Learn how to build a home automation system and we'll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD

Wireless Communication Protocols

The ESP32 supports several different wireless communication protocols. Each protocol has its advantages and disadvantages and one can be more suitable than the other depending on the application. This guide is a compilation of all our articles about wireless communication protocols with the ESP32.

Communication Protocols

In this article, we'll cover the following communication protocols: BLE (Bluetooth Low Energy) Bluetooth Classic ESP-NOW Wi-Fi MQTT LoRa GSM/GPRS/LTE We'll keep this article updated as new tutorials are posted.

Bluetooth Low Energy (BLE)

Bluetooth Low Energy, BLE for short, is a power-conserving variant of Bluetooth. BLE's primary application is short-distance transmission of small amounts of data (low bandwidth). Unlike Bluetooth which is always on, BLE remains in sleep mode constantly except for when a connection is initiated. This makes it consume very low power. BLE consumes approximately 100x less power than Bluetooth (depending on the use case). Additionally, BLE supports not only point-to-point communication, but also broadcast mode, and mesh network. Due to its properties, BLE is suitable for applications that need to exchange small amounts of data periodically running on a coin cell. For example, BLE is of great use in healthcare, fitness, tracking, beacons, security, and home automation industries. Low power consumption Short distance transmission Low bandwidth (small amounts of data) Ideal for exchanging small amounts of data periodically Supports point-to-point, broadcast, and mesh network Learn how to get started with BLE on the ESP32 with our guides: Getting Started with ESP32 Bluetooth Low Energy (BLE) on Arduino IDE ESP32 BLE Server and Client (Bluetooth Low Energy)

Bluetooth Classic

Bluetooth is a wireless technology standard used for exchanging data between fixed and mobile devices over short distances. It is optimized for continuous data streaming, while BLE is optimized for short burst data transmission. It consumes approximately x100 more power than BLE. Short distance transmission Optimized for continuous data streaming Learn how to use Bluetooth Classic with the ESP32: ESP32 Bluetooth Classic with Arduino IDE Getting Started

ESP-NOW

ESP-NOW is a connectionless communication protocol developed by Espressif that features short packet transmission. This protocol enables multiple devices to talk to each other in an easy way. Stating the Espressif website, ESP-NOW is a protocol developed by Espressif, which enables multiple devices to communicate with one another without using Wi-Fi. The protocol is similar to the low-power 2.4GHz wireless connectivity (). The pairing between devices is needed prior to their communication. After the pairing is done, the connection is safe and peer-to-peer, with no handshake being required. Fast communication protocol Up to 250-byte payload can be carried Encrypted and unencrypted communication Range communication (220 meters in opan en field accordingly to our experiments) Read our articles about ESP-NOW: Getting Started with ESP-NOW (ESP32 with Arduino IDE) ESP-NOW Two-Way Communication Between ESP32 Boards More ESP-NOW tutorials

Wi-Fi (client-server communication protocols)

HTTP Requests

You can exchange data between ESP32 boards using HTTP requests. One board acts as a server (Wi-Fi access point) and the other board acts as a client (Wi-Fi station). Learn how to send data from one ESP32 board to the other using HTTP requests: ESP32 Client-Server Wi-Fi Communication Between Two Boards The ESP32 can also make HTTP requests to third-party services on the internet to send or receive data. For that, the ESP32 needs to be connected to a Wi-Fi network with internet access. ESP32 HTTP GET and HTTP POST with Arduino IDE (JSON, URL Encoded, Text)

Server-Sent Events

Server-Sent Events (SSE) allow the client to receive automatic updates from a server via HTTP connection. The client initiates the SSE connection and the server uses the event source protocol to send updates to the client. The client will receive updates from the server, but it can't send any data to the server after the initial handshake. Server-sent events are useful when you need to send new data to the server without the need for a request by the server, for example send sensor readings periodically or notifications. Learn how to use server-sent events on an ESP32 web server: ESP32 Web Server using Server-Sent Events

WebSocket

A WebSocket is a persistent connection between a client and server that allows bidirectional communication between both parties using a TCP connection. This means you can send data from the client to the server and from the server to the client at any given time. Get started with WebSocket protocol on the ESP32 by following the next tutorial: ESP32 WebSocket Server: Control Outputs (Arduino IDE)

MQTT

MQTT stands for Message Queuing Telemetry Transport. It is a lightweight publish and subscribe system where you can publish and receive messages as a client. MQTT is a simple messaging protocol, designed for constrained devices with low-bandwidth. To use MQTT to exchange data, you need an MQTT broker that is responsible for receiving all messages, filtering the messages, and publishing the message to all subscribed clients. MQTT is perfect for IoT projects with multiple devices. Read our articles about the MQTT communication protocol with the ESP32. What is MQTT and How It Works ESP32 MQTT Publish and Subscribe with Arduino IDE MicroPython Getting Started with MQTT on ESP32/ESP8266

LoRa

LoRa is a wireless data communication technology that uses a radio modulation technique that can be generated by Semtech LoRa transceiver chips. LoRa allows long range communication of small amounts of data (which means a low bandwidth), and high immunity to interference while minimizing power consumption. So, it allows long distance communication with low power requirements. Long range communication Low bandwidth (small amounts of data) High immunity to interference Low power consumption To use LoRa with the ESP32 boards, you need a LoRa transceiver chip. The word transceiver means that the chip can send and receive LoRa messages. There are ESP32 boards that already come with an on-board LoRa transceiver chip, which makes wiring much simpler. Read our articles about LoRa communication: ESP32 with LoRa using Arduino IDE Getting Started learn what is LoRa, how to connect a LoRa chip to the ESP32, and exchange data between boards. TTGO LoRa32 SX1276 OLED Board: Getting Started with Arduino IDE learn how to get started with LoRa with a board with a built-in LoRa chip and OLED display. ESP32 LoRa Sensor Monitoring with Web Server (Long Range Communication) set up an ESP32 as a LoRa receiver and as a web server to display received readings.

GSM/GPRS/LTE

You can connect your ESP32 board to a modem to be able to send and receive SMS and phone calls and connect to the internet using a SIM card as you would do with your smartphone. Some of those modems can also get GPS data like latitude, longitude, altitude, and date and time. There are different modules available that are compatible with the ESP32 and there are also ESP32 boards that already come with a built-in modem and all the necessary circuitry. We've experimented with the ESP32 SIM8000L (2G), and the ESP32 SIM7000G (3G and 4G), and we had pretty good results. To get started with those boards, you can take a look at the following tutorials: Getting Started with LILYGO T-SIM7000G ESP32 (LTE, GPRS, and GPS) ESP32 Publish Data to Cloud without Wi-Fi (TTGO T-Call ESP32 SIM800L) ESP32 SIM800L: Send Text Messages (SMS Alert) with Sensor Readings Connect ESP32 to Cloud MQTT Broker (TTGO T-Call ESP32 SIM800L)

Wrapping Up

There are many different wireless communication protocols compatible with the ESP32 boards. This makes it one of the most versatile boards for IoT and Home Automation projects. In this article, we've covered LoRa, Bluetooth, Bluetooth Low Energy, ESP-NOW, Wi-Fi, MQTT, and GSM/GPRS/LTE.

ESP-NOW Encrypted Messages

In this guide, you'll learn how to encrypt ESP-NOW messages exchanged between ESP32 boards. ESP-NOW uses the CCMP method for encryption using a Primary Master Key (PMK) and Local Master Keys (LMK). If you're new to ESP-NOW, we recommend reading the following getting started guide first to get familiar with ESP-NOW concepts and functions on the ESP32: Getting Started with ESP-NOW (ESP32 with Arduino IDE)

CCMP Security Protocol

CCMP means Counter Mode with Cipher Block Chaining Message Authentication Code Protocol. This is an encryption protocol designed for Wireless LAN. ESP-NOW can use the CCMP method to encrypt messages. Accordingly to the documentation: ESP-NOW use CCMP method which can be referenced in IEEE Std. 802.11-2012 to protect the vendor-specific action frame. The Wi-Fi device maintains a Primary Master Key (PMK) and several Local Master Keys (LMK). The length of the keys is 16 bytes.

Primary Master Key (PMK)

PMK is used to encrypt LMK with the AES-128 algorithm. To set the PMK key of the Wi-Fi device, you can use the esp_now_set_pmk() function to set PMK. If PMK is not set, a default PMK will be used.

Local Master Key (LMK)

You should set the LMK of the paired device to encrypt the vendor-specific action frame with CCMP method. The maximum number of different LMKs is six. The LMK is a property of the peer, esp_now_peer_info_t object, and can be set on the lmk property as we'll see later.

ESP32: Getting Board MAC Address

To communicate via ESP-NOW, you need to know the MAC Addresses of the boards so that you can add each other as peers. Each ESP32 has a unique MAC Address and that's how we identify each board (learn how to Get and Change the ESP32 MAC Address). To get your board's MAC Address, upload the following code. // Complete Instructions to Get and Change ESP MAC Address: https://RandomNerdTutorials.com/get-change-esp32-esp8266-mac-address-arduino/ #include "WiFi.h" void setup(){ Serial.begin(115200); WiFi.mode(WIFI_MODE_STA); Serial.println(WiFi.macAddress()); } void loop(){ } View raw code After uploading the code, open the Serial Monitor at a baud rate of 115200 and press the ESP32 RST/EN button. The MAC address should be printed as follows: Save your board MAC address because you'll need it in the next ESP-NOW examples.

Project Overview

The example we'll show you is very simple so that you can understand how to encrypt your ESP-NOW messages. The sender will send a structure that contains two random numbers, x and y, and a counter variable (to keep track of the number of sent packets). Here are the main steps:
    The sender sets its PMK; The sender adds the receiver as a peer and sets its LMK; The receiver sets its PMK (should be the same of the receiver); The receiver adds the sender as a peer and sets its LMK (should be the same as the one set on the sender board); The sender sends the following structure to the receiver board:
typedef struct struct_message { int counter; int x; int y; } struct_message;
    The receiver gets the message.

ESP32 Sender Sketch (ESP-NOW Encrypted)

Here's the code for the ESP32 Sender board. Copy the code to your Arduino IDE, but don't upload it yet. You need to make a few modifications to make it work for you. /* Rui Santos Complete project details at https://RandomNerdTutorials.com/?s=esp-now Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ #include <esp_now.h> #include <WiFi.h> // REPLACE WITH THE RECEIVER'S MAC Address uint8_t receiverAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // PMK and LMK keys static const char* PMK_KEY_STR = "REPLACE_WITH_PMK_KEY"; static const char* LMK_KEY_STR = "REPLACE_WITH_LMK_KEY"; // Structure example to send data // Must match the receiver structure typedef struct struct_message { int counter; int x; int y; } struct_message; // Create a struct_message called myData struct_message myData; // Counter variable to keep track of number of sent packets int counter; // callback when data is sent void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { Serial.print("\r\nLast Packet Send Status:\t"); Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail"); } void setup() { // Init Serial Monitor Serial.begin(115200); // Set device as a Wi-Fi Station WiFi.mode(WIFI_STA); // Init ESP-NOW if (esp_now_init() != ESP_OK) { Serial.println("There was an error initializing ESP-NOW"); return; } // Set PMK key esp_now_set_pmk((uint8_t *)PMK_KEY_STR); // Register the receiver board as peer esp_now_peer_info_t peerInfo; memcpy(peerInfo.peer_addr, receiverAddress, 6); peerInfo.channel = 0; //Set the receiver device LMK key for (uint8_t i = 0; i < 16; i++) { peerInfo.lmk[i] = LMK_KEY_STR[i]; } // Set encryption to true peerInfo.encrypt = true; // Add receiver as peer if (esp_now_add_peer(&peerInfo) != ESP_OK){ Serial.println("Failed to add peer"); return; } // Once ESPNow is successfully Init, we will register for Send CB to // get the status of transmitted packet esp_now_register_send_cb(OnDataSent); } void loop() { static unsigned long lastEventTime = millis(); static const unsigned long EVENT_INTERVAL_MS = 5000; if ((millis() - lastEventTime) > EVENT_INTERVAL_MS) { lastEventTime = millis(); // Set values to send myData.counter = counter++; myData.x = random(0,50); myData.y = random(0,50); // Send message via ESP-NOW esp_err_t result = esp_now_send(receiverAddress, (uint8_t *) &myData, sizeof(myData)); if (result == ESP_OK) { Serial.println("Sent with success"); } else { Serial.println("Error sending the data"); } } } View raw code Don't forget you need to add the receiver's MAC address in the code. In my case, the receiver MAC address is 30:AE:A4:07:0D:64. So, it will look as follows on the code: // REPLACE WITH THE RECEIVER'S MAC Address uint8_t receiverAddress[] = {0x30, 0xAE, 0xA4, 0x07, 0x0D, 0x64}; Let's take a look at the relevant parts of code that deal with encryption. Create the PMK and LMK keys for this device on the following lines. It can be made of numbers and letters and the keys are 16 bytes (you can search online for online byte counter to check the length of your keys). static const char* PMK_KEY_STR = "REPLACE_WITH_PMK_KEY"; static const char* LMK_KEY_STR = "REPLACE_WITH_LMK_KEY"; For example, the key can be something like this 00XXmkwei/lpPf. The sender and receiver should have the same PMK and LMK keys. Set the device PMK key using the esp_now_set_pmk() function as follows: esp_now_set_pmk((uint8_t *)PMK_KEY_STR); The LMK is a property of the peer device, so you must set it when you register a device as peer. You set the LMK as follows: for (uint8_t i = 0; i < 16; i++) { peerInfo.lmk[i] = LMK_KEY_STR[i]; } You also need to set the encrypt peer property as true. peerInfo.encrypt = true; And that's it. This is all you need to do to encrypt ESP-NOW messages. Now, you can use the ESP-NOW function to exchange data and the messages will be encrypted.

ESP32 Receiver Sketch (ESP-NOW Encrypted Messages)

Here's the code for the ESP32 Receiver board. Copy the code to your Arduino IDE, but don't upload it yet. You need to make a few modifications to make it work for you. /* Rui Santos Complete project details at https://RandomNerdTutorials.com/?s=esp-now Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ #include <esp_now.h> #include <WiFi.h> // REPLACE WITH YOUR MASTER MAC Address uint8_t masterMacAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // PMK and LMK keys static const char* PMK_KEY_STR = "REPLACE_WITH_PMK_KEY"; static const char* LMK_KEY_STR = "REPLACE_WITH_LMK_KEY"; // Structure example to send data // Must match the sender structure typedef struct struct_message { int counter; // must be unique for each sender board int x; int y; } struct_message; // Create a struct_message called myData struct_message myData; // Function to print MAC address on Serial Monitor void printMAC(const uint8_t * mac_addr){ char macStr[18]; snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x", mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); Serial.println(macStr); } // Callback function executed when data is received void OnDataRecv(const uint8_t * mac_addr, const uint8_t *incomingData, int len) { Serial.print("Packet received from: "); printMAC(mac_addr); memcpy(&myData, incomingData, sizeof(myData)); Serial.print("Bytes received: "); Serial.println(len); Serial.print("Packet number: "); Serial.println(myData.counter); Serial.print("X: "); Serial.println(myData.x); Serial.print("Y: "); Serial.println(myData.y); } void setup() { // Init Serial Monitor Serial.begin(115200); // Set device as a Wi-Fi Station WiFi.mode(WIFI_STA); // Init ESP-NOW if (esp_now_init() != ESP_OK) { Serial.println("There was an error initializing ESP-NOW"); return; } // Set the PMK key esp_now_set_pmk((uint8_t *)PMK_KEY_STR); // Register the master as peer esp_now_peer_info_t peerInfo; memcpy(peerInfo.peer_addr, masterMacAddress, 6); peerInfo.channel = 0; // Setting the master device LMK key for (uint8_t i = 0; i < 16; i++) { peerInfo.lmk[i] = LMK_KEY_STR[i]; } // Set encryption to true peerInfo.encrypt = true; // Add master as peer if (esp_now_add_peer(&peerInfo) != ESP_OK){ Serial.println("Failed to add peer"); return; } // Once ESPNow is successfully Init, we will register for recv CB to // get recv packer info esp_now_register_recv_cb(OnDataRecv); } void loop() { } View raw code You need to add the sender board as a peer. So, you need to know its MAC address. Add the sender MAC address in the following line: uint8_t masterMacAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; Set the PMK and LMK keys. Should be the same as the other board. static const char* PMK_KEY_STR = "REPLACE_WITH_PMK_KEY"; static const char* LMK_KEY_STR = "REPLACE_WITH_LMK_KEY"; Set the device PMK key using the esp_now_set_pmk() function as follows: esp_now_set_pmk((uint8_t *)PMK_KEY_STR); The LMK is a property of the peer device, so you must set it when you register a device as peer. You set the LMK as follows: for (uint8_t i = 0; i < 16; i++) { peerInfo.lmk[i] = LMK_KEY_STR[i];} You also need to set the encrypt peer property as true. peerInfo.encrypt = true; And that's it, now the receiver board can receiver and decrypt the encrypted messages sent by the sender.

Demonstration

Upload the codes to the corresponding boards. Open the Serial Monitor to check what's going on. You can use PuTTY to be able to see the messages on both boards simultaneously. This is what you should get on the receiver board: On the sender board, you should get Delivery Success messages.

Wrapping Up

In this tutorial, you learned how to encrypt ESP-NOW messages using PMK and LMK keys. I tested the encryption in different scenarios and here are the results: Sender and receiver encrypted with same keys: receiver board receives the messages successfully; The sender sends encrypted messages, but the receiver doesn't have the keys or has different keys: the receiver doesn't get the messages; The receiver has the code for encryption but the sender doesn't: the receiver gets the messages anyway. I don't think this is the behavior we expected. Since we add the encryption code on both boards, one would expect that if the receiver board got a message that is not encrypted, it would ignore it. But that's not what happens. It receives all messages, encrypted and not encrypted. At the moment, there isn't a way to know if the received message is encrypted or not, which seems like a limitation at the moment.

ESP-NOW: Auto-pairing for ESP32/ESP8266 with Bidirectional Communication and Web Server

This guide shows how to build an ESP32 web server and use ESP-NOW communication protocol simultaneously. We'll show you how to establish a two-way communication between the master (web server) and slaves, and how to automatically add boards to the network (auto-pairing). This tutorial is an improvement of the following: ESP32: ESP-NOW Web Server Sensor Dashboard (ESP-NOW + Wi-Fi) The new version includes: Two-way communication between the server and the slaves; Auto-pairing peersyou don't need to know any of the boards' MAC addresses. You don't need to add peers manually. You just need to run the codes provided and the boards will be automatically added to the ESP-NOW network. The improvements were suggested by one of our readers (Jean-Claude Servaye). You can find the original codes on his GitHub page. If you're new to ESP-NOW, we recommend getting familiar with ESP-NOW concepts and functions first. Check the following getting started guides: Getting Started with ESP-NOW (ESP32 with Arduino IDE) Getting Started with ESP-NOW (ESP8266 NodeMCU with Arduino IDE) ESP-NOW Two-Way Communication Between ESP32 Boards ESP-NOW Two-Way Communication Between ESP8266 NodeMCU Boards

Using ESP-NOW and Wi-Fi (Web Server) Simultaneously

There are a few things you need to take into account if you want to use Wi-Fi to host a web server and use ESP-NOW simultaneously to receive sensor readings from other boards: The ESP32/ESP8266 sender boards must use the same Wi-Fi channel as the receiver board (server). The Wi-Fi channel of the receiver board is automatically assigned by your Wi-Fi router. The Wi-Fi mode of the receiver board must be access point and station (WIFI_AP_STA). You can set up the same Wi-Fi channel manually, but we'll do it automatically. The sender will try different Wi-Fi channels until it gets a response from the server.

Project Overview

Here's a quick overview of the example we'll build: There are two ESP sender boards (ESP32 or ESP8266) that send readings* via ESP-NOW to one ESP32 receiver board (ESP-NOW many to one configuration); The receiver board receives the packets and displays the readings on a web page; The web page is updated automatically every time it receives a new reading using Server-Sent Events (SSE); The receiver also sends data to the senderthis is to illustrate how to establish bidirectional communication. As an example, we'll send arbitrary values, but you can easily replace them with sensor readings or any other data like threshold values, or commands to turn on/off GPIOs. *we'll send arbitrary temperature and humidity valueswe won't use an actual sensor. After testing the project and checking that everything is working as expected you can use a sensor of your choice (it doesn't have to be temperature or humidity).

Auto-Pairing

Here's how the auto-pairing with peers (sender(server)/slave boards) works: The peer sends a message of type PAIRING to the server (1) using the broadcast MAC address ff:ff:ff:ff:ff:ff. When you send data to this MAC address, all ESP-NOW devices receive the message. For the server to receive the message, they need to communicate on the same Wi-Fi channel. If the peer doesn't receive a message from the server, it tries to send the same message on a different Wi-Fi channel. It repeats the process until it gets a message from the server. The server receives the message and the address of the peer (2). The server adds the address of the peer to his peer list (3). The server replies to the peer with a message of type PAIRING with its information (MAC address and channel) (4). The peer receives the message and the WiFi.macAddress of the server (5). The peer adds the received address of the server to his peer list (6). The peer tries to send a message to the server address but it fails to transmit*.
    The peer adds the WiFi.softAPmacAddress of the server to his peer list. The peer sends a message to the server WiFi.softAPmacAddress.
The server receives the message from the peer. They can now communicate bidirectionally (6). *ESP32 in WIFI_AP_STA mode responds with its WiFi.macAddress but it uses WiFi.softAPmacAddress to receive from ESP8266 peer. WiFi.softAPmacAddress is created from WiFi.macAddress by adding 1 to the last bytecheck the documentation.

Prerequisites

Before proceeding with this project, make sure you check the following prerequisites.

Arduino IDE

We'll program the ESP32 and ESP8266 boards using Arduino IDE, so before proceeding with this tutorial, make sure you have the ESP32 and ESP8266 boards installed in your Arduino IDE. Installing ESP32 Board in Arduino IDE (Windows, Mac OS X, and Linux) Installing ESP8266 Board in Arduino IDE (Windows, Mac OS X, Linux)

Async Web Server Libraries

To build the web server you need to install the following libraries: ESPAsyncWebServer AsyncTCP These libraries aren't available to install through the Arduino Library Manager, so you need to copy the library files to the Arduino Installation Libraries folder. Alternatively, in your Arduino IDE, you can go to Sketch > Include Library > Add .zip Library and select the libraries you've just downloaded.

Arduino_JSON Library

Our examples will use the ArduinoJSON library by Benoit Blanchon version 6.18.3. You can install this library in the Arduino IDE Library Manager. Just go to Sketch > Include Library > Manage Libraries and search for the library name ArduinoJSON as follows:

Parts Required

To test this project, you need at least three ESP boards. One ESP32 board to act as a server and two sender/slave ESP boards that can be ESP32 or ESP8266. ESP32 (read Best ESP32 development boards) ESP8266 (read Best ESP8266 development boards)

ESP32 Server

Here are the server features: Pairs automatically with peers (other ESP-NOW boards); Receives packets from peers; Hosts a web server to display the latest received packets; Also sends data back to the other boards (bidirectional communication with peers).

ESP32 Server Code

Upload the following code to your ESP32 board. This can receive data from multiple boards. However, the web page is just prepared to display data from two boards. You can easily modify the web page to accommodate more boards. /* Rui Santos Complete project details at https://RandomNerdTutorials.com/?s=esp-now Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Based on JC Servaye example: https://github.com/Servayejc/esp_now_web_server/ */ #include <esp_now.h> #include <WiFi.h> #include "ESPAsyncWebServer.h" #include "AsyncTCP.h" #include <ArduinoJson.h> // Replace with your network credentials (STATION) const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; esp_now_peer_info_t slave; int chan; enum MessageType {PAIRING, DATA,}; MessageType messageType; int counter = 0; // Structure example to receive data // Must match the sender structure typedef struct struct_message { uint8_t msgType; uint8_t id; float temp; float hum; unsigned int readingId; } struct_message; typedef struct struct_pairing { // new structure for pairing uint8_t msgType; uint8_t id; uint8_t macAddr[6]; uint8_t channel; } struct_pairing; struct_message incomingReadings; struct_message outgoingSetpoints; struct_pairing pairingData; AsyncWebServer server(80); AsyncEventSource events("/events"); const char index_html[] PROGMEM = R"rawliteral( <!DOCTYPE HTML><html> <head> <title>ESP-NOW DASHBOARD</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous"> <link rel="icon" href="data:,"> <style> html {font-family: Arial; display: inline-block; text-align: center;} p { font-size: 1.2rem;} body { margin: 0;} .topnav { overflow: hidden; background-color: #2f4468; color: white; font-size: 1.7rem; } .content { padding: 20px; } .card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); } .cards { max-width: 700px; margin: 0 auto; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); } .reading { font-size: 2.8rem; } .packet { color: #bebebe; } .card.temperature { color: #fd7e14; } .card.humidity { color: #1b78e2; } </style> </head> <body> <div> <h3>ESP-NOW DASHBOARD</h3> </div> <div> <div> <div> <h4><i></i> BOARD #1 - TEMPERATURE</h4><p><span><span></span> °C</span></p><p>Reading ID: <span></span></p> </div> <div> <h4><i></i> BOARD #1 - HUMIDITY</h4><p><span><span></span> %</span></p><p>Reading ID: <span></span></p> </div> <div> <h4><i></i> BOARD #2 - TEMPERATURE</h4><p><span><span></span> °C</span></p><p>Reading ID: <span></span></p> </div> <div> <h4><i></i> BOARD #2 - HUMIDITY</h4><p><span><span></span> %</span></p><p>Reading ID: <span></span></p> </div> </div> </div> <script> if (!!window.EventSource) { var source = new EventSource('/events'); source.addEventListener('open', function(e) { console.log("Events Connected"); }, false); source.addEventListener('error', function(e) { if (e.target.readyState != EventSource.OPEN) { console.log("Events Disconnected"); } }, false); source.addEventListener('message', function(e) { console.log("message", e.data); }, false); source.addEventListener('new_readings', function(e) { console.log("new_readings", e.data); var obj = JSON.parse(e.data); document.getElementById("t"+obj.id).innerHTML = obj.temperature.toFixed(2); document.getElementById("h"+obj.id).innerHTML = obj.humidity.toFixed(2); document.getElementById("rt"+obj.id).innerHTML = obj.readingId; document.getElementById("rh"+obj.id).innerHTML = obj.readingId; }, false); } </script> </body> </html>)rawliteral"; void readDataToSend() { outgoingSetpoints.msgType = DATA; outgoingSetpoints.id = 0; outgoingSetpoints.temp = random(0, 40); outgoingSetpoints.hum = random(0, 100); outgoingSetpoints.readingId = counter++; } // ---------------------------- esp_ now ------------------------- void printMAC(const uint8_t * mac_addr){ char macStr[18]; snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x", mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); Serial.print(macStr); } bool addPeer(const uint8_t *peer_addr) { // add pairing memset(&slave, 0, sizeof(slave)); const esp_now_peer_info_t *peer = &slave; memcpy(slave.peer_addr, peer_addr, 6); slave.channel = chan; // pick a channel slave.encrypt = 0; // no encryption // check if the peer exists bool exists = esp_now_is_peer_exist(slave.peer_addr); if (exists) { // Slave already paired. Serial.println("Already Paired"); return true; } else { esp_err_t addStatus = esp_now_add_peer(peer); if (addStatus == ESP_OK) { // Pair success Serial.println("Pair success"); return true; } else { Serial.println("Pair failed"); return false; } } } // callback when data is sent void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { Serial.print("Last Packet Send Status: "); Serial.print(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success to " : "Delivery Fail to "); printMAC(mac_addr); Serial.println(); } void OnDataRecv(const uint8_t * mac_addr, const uint8_t *incomingData, int len) { Serial.print(len); Serial.print(" bytes of data received from : "); printMAC(mac_addr); Serial.println(); StaticJsonDocument<1000> root; String payload; uint8_t type = incomingData[0]; // first message byte is the type of message switch (type) { case DATA : // the message is data type memcpy(&incomingReadings, incomingData, sizeof(incomingReadings)); // create a JSON document with received data and send it by event to the web page root["id"] = incomingReadings.id; root["temperature"] = incomingReadings.temp; root["humidity"] = incomingReadings.hum; root["readingId"] = String(incomingReadings.readingId); serializeJson(root, payload); Serial.print("event send :"); serializeJson(root, Serial); events.send(payload.c_str(), "new_readings", millis()); Serial.println(); break; case PAIRING: // the message is a pairing request memcpy(&pairingData, incomingData, sizeof(pairingData)); Serial.println(pairingData.msgType); Serial.println(pairingData.id); Serial.print("Pairing request from: "); printMAC(mac_addr); Serial.println(); Serial.println(pairingData.channel); if (pairingData.id > 0) { // do not replay to server itself if (pairingData.msgType == PAIRING) { pairingData.id = 0; // 0 is server // Server is in AP_STA mode: peers need to send data to server soft AP MAC address WiFi.softAPmacAddress(pairingData.macAddr); pairingData.channel = chan; Serial.println("send response"); esp_err_t result = esp_now_send(mac_addr, (uint8_t *) &pairingData, sizeof(pairingData)); addPeer(mac_addr); } } break; } } void initESP_NOW(){ // Init ESP-NOW if (esp_now_init() != ESP_OK) { Serial.println("Error initializing ESP-NOW"); return; } esp_now_register_send_cb(OnDataSent); esp_now_register_recv_cb(OnDataRecv); } void setup() { // Initialize Serial Monitor Serial.begin(115200); Serial.println(); Serial.print("Server MAC Address: "); Serial.println(WiFi.macAddress()); // Set the device as a Station and Soft Access Point simultaneously WiFi.mode(WIFI_AP_STA); // Set device as a Wi-Fi Station WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Setting as a Wi-Fi Station.."); } Serial.print("Server SOFT AP MAC Address: "); Serial.println(WiFi.softAPmacAddress()); chan = WiFi.channel(); Serial.print("Station IP Address: "); Serial.println(WiFi.localIP()); Serial.print("Wi-Fi Channel: "); Serial.println(WiFi.channel()); initESP_NOW(); // Start Web server server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ request->send_P(200, "text/html", index_html); }); // Events events.onConnect([](AsyncEventSourceClient *client){ if(client->lastId()){ Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId()); } // send event with message "hello!", id current millis // and set reconnect delay to 1 second client->send("hello!", NULL, millis(), 10000); }); server.addHandler(&events); // start server server.begin(); } void loop() { static unsigned long lastEventTime = millis(); static const unsigned long EVENT_INTERVAL_MS = 5000; if ((millis() - lastEventTime) > EVENT_INTERVAL_MS) { events.send("ping",NULL,millis()); lastEventTime = millis(); readDataToSend(); esp_now_send(NULL, (uint8_t *) &outgoingSetpoints, sizeof(outgoingSetpoints)); } } View raw code

How the Code Works

We already explained how the server code works in great detail in a previous project. So, we'll just take a look at the relevant parts for auto-pairing.

Message Types

The server and senders can exchange two types of messages: messages with pairing data with MAC address, channel, and board id, and messages with the actual data like sensor readings. So, we create an enumerated type that holds the possible incoming message types (PAIRING and DATA). enum MessageType {PAIRING, DATA,}; An enumerated type is a data type (usually user-defined) consisting of a set of named constants called enumerators. The act of creating an enumerated type defines an enumeration. When an identifier such as a variable is declared having an enumerated type, the variable can be assigned any of the enumerators as a value. Source: https://playground.arduino.cc/Code/Enum/ After that, we create a variable of that type we've just created called messageType. Remember that this variable can only have two possible values: PAIRING or DATA. MessageType messageType;

Data Structure

Create a structure that will contain the data we'll receive. We called this structure struct_message and it contains the message type (so that we know if we received a message with data or with peer info), board ID, temperature and humidity readings, and the reading ID. typedef struct struct_message { uint8_t msgType; uint8_t id; float temp; float hum; unsigned int readingId; } struct_message; We also need another structure to contain the peer information for pairing the peer. We call this structure struct_pairing. This structure will contain the message type, board id, mac address of the sender board, and Wi-Fi channel. typedef struct struct_pairing { // new structure for pairing uint8_t msgType; uint8_t id; uint8_t macAddr[6]; uint8_t channel; } struct_pairing; We create two variables of type struct_message, one called incomingReadings that will store the readings coming from the slaves, and another called outgoingSetpoints that will hold the data to send to the slaves. struct_message incomingReadings; struct_message outgoingSetpoints; We also create a variable of type struct_pairing to hold the peer information. struct_pairing pairingData;

readDataToSend() Function

The readDataToSend() should be used to get data from whichever sensor you're using and put them on the associated structure to be sent to the slave boards. void readDataToSend() { outgoingSetpoints.msgType = DATA; outgoingSetpoints.id = 0; outgoingSetpoints.temp = random(0, 40); outgoingSetpoints.hum = random(0, 100); outgoingSetpoints.readingId = counter++; } The msgType should be DATA. The id corresponds to the board id (we're setting the server board ID to 0, the others boards should have id=1, 2, 3, and so on). Finally, temp and hum hold the sensor readings. In this case, we're setting them to random values. You should replace that with the correct functions to get data from your sensor. Every time we send a new set of readings, we increase the counter variable.

Adding a Peer

We create a function called addPeer() that will return a boolean variable (either true or false) that indicates whether the pairing process was successful or not. This function tries to add peers. It will be called later when the board receives a message of type PAIRING. If the peer is already on the list of peers, it returns true. It also returns true if the peer is successfully added. It returns false, if it fails to add the peer to the list. bool addPeer(const uint8_t *peer_addr) { // add pairing memset(&slave, 0, sizeof(slave)); const esp_now_peer_info_t *peer = &slave; memcpy(slave.peer_addr, peer_addr, 6); slave.channel = chan; // pick a channel slave.encrypt = 0; // no encryption // check if the peer exists bool exists = esp_now_is_peer_exist(slave.peer_addr); if (exists) { // Slave already paired. Serial.println("Already Paired"); return true; } else { esp_err_t addStatus = esp_now_add_peer(peer); if (addStatus == ESP_OK) { // Pair success Serial.println("Pair success"); return true; } else { Serial.println("Pair failed"); return false; } } }

Receiving and Handling ESP-NOW Messages

The OnDataRecv() function will be executed when you receive a new ESP-NOW packet. void OnDataRecv(const uint8_t * mac_addr, const uint8_t *incomingData, int len) { Inside that function, print the length of the message and the sender's MAC address: Serial.print(len); Serial.print(" bytes of data received from : "); printMAC(mac_addr); Previously, we've seen that we can receive two types of messages: PAIRING and DATA. So, we must handle the message content differently depending on the type of message. We can get the type of message as follows: uint8_t type = incomingData[0]; // first message byte is the type of message Then, we'll run different codes depending if the message is of type DATA or PAIRING. If it is of type DATA, copy the information in the incomingData variable into the incomingReadings structure variable. memcpy(&incomingReadings, incomingData, sizeof(incomingReadings)); Then, create a JSON document with the received information (root): // create a JSON document with received data and send it by event to the web page root["id"] = incomingReadings.id; root["temperature"] = incomingReadings.temp; root["humidity"] = incomingReadings.hum; root["readingId"] = String(incomingReadings.readingId); Convert the JSON document to a string (payload): serializeJson(root, payload); After gathering all the received data on the payload variable, send that information to the browser as an event (new_readings). events.send(payload.c_str(), "new_readings", millis()); We've seen on a previous project how to handle these events on the client side. If the message is of type PAIRING, it contains the peer information. case PAIRING: // the message is a pairing request We save the received data in the incomingData variable and print the details on the Serial Monitor. memcpy(&pairingData, incomingData, sizeof(pairingData)); Serial.println(pairingData.msgType); Serial.println(pairingData.id); Serial.print("Pairing request from: "); printMAC(mac_addr); Serial.println(); Serial.println(pairingData.channel); The server responds back with its MAC address (in access point mode) and channel, so that the peer knows it sent the information using the right channel and can add the server as peer. if (pairingData.id > 0) { // do not replay to server itself if (pairingData.msgType == PAIRING) { pairingData.id = 0; // 0 is server // Server is in AP_STA mode: peers need to send data to server soft AP MAC address WiFi.softAPmacAddress(pairingData.macAddr); pairingData.channel = chan; Serial.println("send response"); esp_err_t result = esp_now_send(mac_addr, (uint8_t *) &pairingData, sizeof(pairingData)); Finally, the server adds the sender to its peer list using the addPeer() function we created previously. addPeer(mac_addr);

Initialize ESP-NOW

The initESP_NOW() function intializes ESP-NOW and registers the callback functions for when data is sent and received. void initESP_NOW(){ // Init ESP-NOW if (esp_now_init() != ESP_OK) { Serial.println("Error initializing ESP-NOW"); return; } esp_now_register_send_cb(OnDataSent); esp_now_register_recv_cb(OnDataRecv); }

setup()

In the setup(), print the board MAC address: Serial.println(WiFi.macAddress()); Set the ESP32 receiver as station and soft access point simultaneously: WiFi.mode(WIFI_AP_STA); The following lines connect the ESP32 to your local network and print the IP address and the Wi-Fi channel: WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Setting as a Wi-Fi Station.."); } Print the board MAC address in access point mode, which is different than the MAC address on station mode. Serial.print("Server SOFT AP MAC Address: "); Serial.println(WiFi.softAPmacAddress()); Get the board Wi-Fi channel and print it in the Serial Monitor. chan = WiFi.channel(); Serial.print("Station IP Address: "); Serial.println(WiFi.localIP()); Serial.print("Wi-Fi Channel: "); Serial.println(WiFi.channel()); Initialize ESP-NOW by calling the initESP_NOW() function we created previously. initESP_NOW();

Send Data Messages to the Sender Boards

In the loop(), every 5 seconds (EVENT_INTERVAL_MS) get data from a sensor or sample data by calling the readDataToSend() function. It adds new data to the outgoingSetpoints structure. readDataToSend(); Finally, send that data to all registered peers. esp_now_send(NULL, (uint8_t *) &outgoingSetpoints, sizeof(outgoingSetpoints)); That's pretty much how the server code works when it comes to handling ESP-NOW messages and automatically adding peers.

Testing the Server

After uploading the code to the receiver board, press the on-board EN/RST button. The ESP32 IP address should be printed on the Serial Monitor as well as the Wi-Fi channel. You can access the web server on the board's IP address. At the moment, there won't be any data displayed because we haven't prepared the sender boards yet. Let the server board run the code.

ESP32/ESP8266 Sender

Here are the sender board features: Pairs automatically with server; Sends packets with sensor readings to server; Also receives data from the server (bidirectional communication).

Auto-Pairing

Here's how the auto-pairing with the server works: The sender doesn't have access to the router; The sender doesn't know the server's MAC address; The server must be running for this to work (with the previous code); The sender sets esp now on channel 1; The server adds an entry with the broadcast address to its peer list; The sender sends a PAIRING message request in broadcast mode: If the server receives the message we are on the correct channel: The server adds the received MAC to his peer list (previous section); The server replies to the MAC address with a message containing his channel number and MAC address (previous section); The sender replaces the broadcast address with the server address in his peer list. elseThe sender repeats the process on the next channel. WiFi.softAPmacAddress is created from WiFi.macAddress by adding 1 to the last bytecheck the documentation.

ESP32 Sender Code

Upload the following code to your ESP32 board. /* Rui Santos Complete project details at https://RandomNerdTutorials.com/?s=esp-now Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Based on JC Servaye example: https://github.com/Servayejc/esp_now_sender/ */ #include <Arduino.h> #include <esp_now.h> #include <esp_wifi.h> #include <WiFi.h> #include <EEPROM.h> // Set your Board and Server ID #define BOARD_ID 1 #define MAX_CHANNEL 11 // for North America // 13 in Europe uint8_t serverAddress[] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}; //Structure to send data //Must match the receiver structure // Structure example to receive data // Must match the sender structure typedef struct struct_message { uint8_t msgType; uint8_t id; float temp; float hum; unsigned int readingId; } struct_message; typedef struct struct_pairing { // new structure for pairing uint8_t msgType; uint8_t id; uint8_t macAddr[6]; uint8_t channel; } struct_pairing; //Create 2 struct_message struct_message myData; // data to send struct_message inData; // data received struct_pairing pairingData; enum PairingStatus {NOT_PAIRED, PAIR_REQUEST, PAIR_REQUESTED, PAIR_PAIRED,}; PairingStatus pairingStatus = NOT_PAIRED; enum MessageType {PAIRING, DATA,}; MessageType messageType; #ifdef SAVE_CHANNEL int lastChannel; #endif int channel = 1; // simulate temperature and humidity data float t = 0; float h = 0; unsigned long currentMillis = millis(); unsigned long previousMillis = 0; // Stores last time temperature was published const long interval = 10000; // Interval at which to publish sensor readings unsigned long start; // used to measure Pairing time unsigned int readingId = 0; // simulate temperature reading float readDHTTemperature() { t = random(0,40); return t; } // simulate humidity reading float readDHTHumidity() { h = random(0,100); return h; } void addPeer(const uint8_t * mac_addr, uint8_t chan){ esp_now_peer_info_t peer; ESP_ERROR_CHECK(esp_wifi_set_channel(chan ,WIFI_SECOND_CHAN_NONE)); esp_now_del_peer(mac_addr); memset(&peer, 0, sizeof(esp_now_peer_info_t)); peer.channel = chan; peer.encrypt = false; memcpy(peer.peer_addr, mac_addr, sizeof(uint8_t[6])); if (esp_now_add_peer(&peer) != ESP_OK){ Serial.println("Failed to add peer"); return; } memcpy(serverAddress, mac_addr, sizeof(uint8_t[6])); } void printMAC(const uint8_t * mac_addr){ char macStr[18]; snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x", mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); Serial.print(macStr); } void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { Serial.print("\r\nLast Packet Send Status:\t"); Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail"); } void OnDataRecv(const uint8_t * mac_addr, const uint8_t *incomingData, int len) { Serial.print("Packet received from: "); printMAC(mac_addr); Serial.println(); Serial.print("data size = "); Serial.println(sizeof(incomingData)); uint8_t type = incomingData[0]; switch (type) { case DATA : // we received data from server memcpy(&inData, incomingData, sizeof(inData)); Serial.print("ID = "); Serial.println(inData.id); Serial.print("Setpoint temp = "); Serial.println(inData.temp); Serial.print("SetPoint humidity = "); Serial.println(inData.hum); Serial.print("reading Id = "); Serial.println(inData.readingId); if (inData.readingId % 2 == 1){ digitalWrite(LED_BUILTIN, LOW); } else { digitalWrite(LED_BUILTIN, HIGH); } break; case PAIRING: // we received pairing data from server memcpy(&pairingData, incomingData, sizeof(pairingData)); if (pairingData.id == 0) { // the message comes from server printMAC(mac_addr); Serial.print("Pairing done for "); printMAC(pairingData.macAddr); Serial.print(" on channel " ); Serial.print(pairingData.channel); // channel used by the server Serial.print(" in "); Serial.print(millis()-start); Serial.println("ms"); addPeer(pairingData.macAddr, pairingData.channel); // add the server to the peer list #ifdef SAVE_CHANNEL lastChannel = pairingData.channel; EEPROM.write(0, pairingData.channel); EEPROM.commit(); #endif pairingStatus = PAIR_PAIRED; // set the pairing status } break; } } PairingStatus autoPairing(){ switch(pairingStatus) { case PAIR_REQUEST: Serial.print("Pairing request on channel " ); Serial.println(channel); // set WiFi channel ESP_ERROR_CHECK(esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE)); if (esp_now_init() != ESP_OK) { Serial.println("Error initializing ESP-NOW"); } // set callback routines esp_now_register_send_cb(OnDataSent); esp_now_register_recv_cb(OnDataRecv); // set pairing data to send to the server pairingData.msgType = PAIRING; pairingData.id = BOARD_ID; pairingData.channel = channel; // add peer and send request addPeer(serverAddress, channel); esp_now_send(serverAddress, (uint8_t *) &pairingData, sizeof(pairingData)); previousMillis = millis(); pairingStatus = PAIR_REQUESTED; break; case PAIR_REQUESTED: // time out to allow receiving response from server currentMillis = millis(); if(currentMillis - previousMillis > 250) { previousMillis = currentMillis; // time out expired, try next channel channel ++; if (channel > MAX_CHANNEL){ channel = 1; } pairingStatus = PAIR_REQUEST; } break; case PAIR_PAIRED: // nothing to do here break; } return pairingStatus; } void setup() { Serial.begin(115200); Serial.println(); pinMode(LED_BUILTIN, OUTPUT); Serial.print("Client Board MAC Address: "); Serial.println(WiFi.macAddress()); WiFi.mode(WIFI_STA); WiFi.disconnect(); start = millis(); #ifdef SAVE_CHANNEL EEPROM.begin(10); lastChannel = EEPROM.read(0); Serial.println(lastChannel); if (lastChannel >= 1 && lastChannel <= MAX_CHANNEL) { channel = lastChannel; } Serial.println(channel); #endif pairingStatus = PAIR_REQUEST; } void loop() { if (autoPairing() == PAIR_PAIRED) { unsigned long currentMillis = millis(); if (currentMillis - previousMillis >= interval) { // Save the last time a new reading was published previousMillis = currentMillis; //Set values to send myData.msgType = DATA; myData.id = BOARD_ID; myData.temp = readDHTTemperature(); myData.hum = readDHTHumidity(); myData.readingId = readingId++; esp_err_t result = esp_now_send(serverAddress, (uint8_t *) &myData, sizeof(myData)); } } } View raw code

ESP8266 Sender Code

If you're using ESP8266 boards, use the following code instead. It's similar to the previous code but uses the ESP8266-specific ESP-NOW functions. /* Rui Santos Complete project details at https://RandomNerdTutorials.com/?s=esp-now Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Based on JC Servaye example: https://https://github.com/Servayejc/esp8266_espnow */ #include <ESP8266WiFi.h> #include <espnow.h> uint8_t channel = 1; int readingId = 0; int id = 2; unsigned long currentMillis = millis(); unsigned long lastTime = 0; unsigned long timerDelay = 2000; // send readings timer uint8_t broadcastAddressX[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; enum PairingStatus {PAIR_REQUEST, PAIR_REQUESTED, PAIR_PAIRED, }; PairingStatus pairingStatus = PAIR_REQUEST; enum MessageType {PAIRING, DATA,}; MessageType messageType; // Define variables to store DHT readings to be sent float temperature; float humidity; // Define variables to store incoming readings float incomingTemp; float incomingHum; int incomingReadingsId; // Updates DHT readings every 10 seconds //const long interval = 10000; unsigned long previousMillis = 0; // will store last time DHT was updated //Structure example to send data //Must match the receiver structure typedef struct struct_message { uint8_t msgType; uint8_t id; float temp; float hum; unsigned int readingId; } struct_message; typedef struct struct_pairing { // new structure for pairing uint8_t msgType; uint8_t id; uint8_t macAddr[6]; uint8_t channel; } struct_pairing; // Create a struct_message called myData struct_message myData; struct_message incomingReadings; struct_pairing pairingData; #define BOARD_ID 2 unsigned long start; // Callback when data is sent void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus) { Serial.print("Last Packet Send Status: "); if (sendStatus == 0){ Serial.println("Delivery success"); } else{ Serial.println("Delivery fail"); } } void printMAC(const uint8_t * mac_addr){ char macStr[18]; snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x", mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); Serial.print(macStr); } void printIncomingReadings(){ // Display Readings in Serial Monitor Serial.println("INCOMING READINGS"); Serial.print("Temperature: "); Serial.print(incomingTemp); Serial.println(" oC"); Serial.print("Humidity: "); Serial.print(incomingHum); Serial.println(" %"); Serial.print("Led: "); Serial.print(incomingReadingsId); } // Callback when data is received void OnDataRecv(uint8_t * mac, uint8_t *incomingData, uint8_t len) { Serial.print("Size of message : "); Serial.print(len); Serial.print(" from "); printMAC(mac); Serial.println(); uint8_t type = incomingData[0]; switch (type) { case DATA : memcpy(&incomingReadings, incomingData, sizeof(incomingReadings)); Serial.print(len); Serial.print(" Data bytes received from: "); printMAC(mac); Serial.println(); incomingTemp = incomingReadings.temp; incomingHum = incomingReadings.hum; printIncomingReadings(); if (incomingReadings.readingId % 2 == 1){ digitalWrite(LED_BUILTIN, LOW); } else { digitalWrite(LED_BUILTIN, HIGH); } break; case PAIRING: memcpy(&pairingData, incomingData, sizeof(pairingData)); if (pairingData.id == 0) { // the message comes from server Serial.print("Pairing done for "); printMAC(pairingData.macAddr); Serial.print(" on channel " ); Serial.print(pairingData.channel); // channel used by the server Serial.print(" in "); Serial.print(millis()-start); Serial.println("ms"); //esp_now_del_peer(pairingData.macAddr); //esp_now_del_peer(mac); esp_now_add_peer(pairingData.macAddr, ESP_NOW_ROLE_COMBO, pairingData.channel, NULL, 0); // add the server to the peer list pairingStatus = PAIR_PAIRED ; // set the pairing status } break; } } void getReadings(){ // Read Temperature temperature = 22.5; humidity = 55.5; } PairingStatus autoPairing(){ switch(pairingStatus) { case PAIR_REQUEST: Serial.print("Pairing request on channel " ); Serial.println(channel); // clean esp now esp_now_deinit(); WiFi.mode(WIFI_STA); // set WiFi channel wifi_promiscuous_enable(1); wifi_set_channel(channel); wifi_promiscuous_enable(0); //WiFi.printDiag(Serial); WiFi.disconnect(); // Init ESP-NOW if (esp_now_init() != 0) { Serial.println("Error initializing ESP-NOW"); } esp_now_set_self_role(ESP_NOW_ROLE_COMBO); // set callback routines esp_now_register_send_cb(OnDataSent); esp_now_register_recv_cb(OnDataRecv); // set pairing data to send to the server pairingData.id = BOARD_ID; pairingData.channel = channel; previousMillis = millis(); // add peer and send request Serial.println(esp_now_send(broadcastAddressX, (uint8_t *) &pairingData, sizeof(pairingData))); pairingStatus = PAIR_REQUESTED; break; case PAIR_REQUESTED: // time out to allow receiving response from server currentMillis = millis(); if(currentMillis - previousMillis > 100) { previousMillis = currentMillis; // time out expired, try next channel channel ++; if (channel > 11) { channel = 0; } pairingStatus = PAIR_REQUEST; } break; case PAIR_PAIRED: //Serial.println("Paired!"); break; } return pairingStatus; } void setup() { // Init Serial Monitor Serial.begin(74880); pinMode(LED_BUILTIN, OUTPUT); // Init DHT sensor // Set device as a Wi-Fi Station WiFi.mode(WIFI_STA); Serial.println(WiFi.macAddress()); WiFi.disconnect(); // Init ESP-NOW if (esp_now_init() != 0) { Serial.println("Error initializing ESP-NOW"); return; } // Set ESP-NOW Role esp_now_set_self_role(ESP_NOW_ROLE_COMBO); // Register for a callback function that will be called when data is received esp_now_register_recv_cb(OnDataRecv); esp_now_register_send_cb(OnDataSent); pairingData.id = 2; } void loop() { if (autoPairing() == PAIR_PAIRED) { static unsigned long lastEventTime = millis(); static const unsigned long EVENT_INTERVAL_MS = 10000; if ((millis() - lastEventTime) > EVENT_INTERVAL_MS) { Serial.print("."); getReadings(); //Set values to send myData.msgType = DATA; myData.id = 2; myData.temp = temperature; myData.hum = humidity; myData.readingId = readingId ++; // Send message via ESP-NOW to all peers esp_now_send(pairingData.macAddr, (uint8_t *) &myData, sizeof(myData)); lastEventTime = millis(); } } } View raw code

How the Code Works

The ESP32 and ESP8266 are slightly different when it comes to the ESP-NOW-specific functions. But they are structured similarly. So, we'll just take a look at the ESP32 code. We'll take a look at the relevant sections that handle auto-pairing with the server. The rest of the code was already explained in great detail in a previous project.

Set Board ID

Define the sender board ID. Each board should have a different id so that the server knows who sent the message. Board id 0 is reserved for the server, so you should start numbering your sender boards at 1. // Set your Board ID (ESP32 Sender #1 = BOARD_ID 1, ESP32 Sender #2 = BOARD_ID 2, etc) #define BOARD_ID 1

Define the maximum number of channels

The sender will loop through different Wi-Fi channels until it finds the server. So, set the maximum number of channels. #define MAX_CHANNEL 11 // for North America // 13 in Europe

Server's MAC Address

The sender board doesn't know the server MAC address. So, we'll start by sending a message to the broadcast MAC address FF:FF:FF:FF:FF:FF on different channels. When we send messages to this MAC address, all ESP-NOW devices receive this message. Then, the server will respond back with its actual MAC address when we find the right Wi-Fi channel. uint8_t serverAddress[] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};

Data Structure

Similarly to the server code, we create two structures. One to receive actual data and another to receive details for pairing. //Structure to send data //Must match the receiver structure // Structure example to receive data // Must match the sender structure typedef struct struct_message { uint8_t msgType; uint8_t id; float temp; float hum; unsigned int readingId; } struct_message; typedef struct struct_pairing { // new structure for pairing uint8_t msgType; uint8_t id; uint8_t macAddr[6]; uint8_t channel; } struct_pairing; //Create 2 struct_message struct_message myData; // data to send struct_message inData; // data received struct_pairing pairingData;

Pairing Statues

Then, we create an enumeration type called ParingStatus that can have the following values: NOT_PAIRED, PAIR_REQUEST, PAIR_REQUESTED, and PAIR_PAIRED. This will help us following the pairing status situation. enum PairingStatus {NOT_PAIRED, PAIR_REQUEST, PAIR_REQUESTED, PAIR_PAIRED,}; We create a variable of that type called pairingStatus. When the board first starts, it's not paired, so it's set to NOT_PAIRED. PairingStatus pairingStatus = NOT_PAIRED;

Message Types

As we did in the server, we also create a MessageType so that we know if we received a pairing message or a message with data. enum MessageType {PAIRING, DATA,}; MessageType messageType;

Adding a Peer

This function adds a new peer to the list. It accepts as arguments the peer MAC address and channel. void addPeer(const uint8_t * mac_addr, uint8_t chan){ esp_now_peer_info_t peer; ESP_ERROR_CHECK(esp_wifi_set_channel(chan ,WIFI_SECOND_CHAN_NONE)); esp_now_del_peer(mac_addr); memset(&peer, 0, sizeof(esp_now_peer_info_t)); peer.channel = chan; peer.encrypt = false; memcpy(peer.peer_addr, mac_addr, sizeof(uint8_t[6])); if (esp_now_add_peer(&peer) != ESP_OK){ Serial.println("Failed to add peer"); return; } memcpy(serverAddress, mac_addr, sizeof(uint8_t[6])); }

Receiving and Handling ESP-NOW Messages

The OnDataRecv() function will be executed when you receive a new ESP-NOW packet. void OnDataRecv(const uint8_t * mac_addr, const uint8_t *incomingData, int len) { Inside that function, print the length of the message and the sender's MAC address: Serial.print("Packet received from: "); printMAC(mac_addr); Serial.println(); Serial.print("data size = "); Serial.println(sizeof(incomingData)); Previously, we've seen that we can receive two types of messages: PAIRING and DATA. So, we must handle the message content differently depending on the type of message. We can get the type of message as follows: uint8_t type = incomingData[0]; // first message byte is the type of message Then, we'll run different codes depending if the message is of type DATA or PAIRING. If it is of type DATA, copy the information in the incomingData variable into the inData structure variable. memcpy(&inData, incomingData, sizeof(inData)); Then, we simply print the received data on the Serial Monitor. You can do any other tasks with the received data that might be useful for your project. Serial.print("ID = "); Serial.println(inData.id); Serial.print("Setpoint temp = "); Serial.println(inData.temp); Serial.print("SetPoint humidity = "); Serial.println(inData.hum); Serial.print("reading Id = "); Serial.println(inData.readingId); In this case, we blink the built-in LED whenever the reading ID is an odd number, but you can perform any other tasks depending on the received data. if (incomingReadings.readingId % 2 == 1){ digitalWrite(LED_BUILTIN, LOW); } else { digitalWrite(LED_BUILTIN, HIGH); } break; If the message is of type PAIRING, first we check if the received message is from the server and not from another sender board. We know that because the id variable for the server is 0. case PAIRING: // we received pairing data from server memcpy(&pairingData, incomingData, sizeof(pairingData)); if (pairingData.id == 0) { // the message comes from server Then, we print the MAC address and channel. This information is sent by the server. Serial.print("Pairing done for "); printMAC(pairingData.macAddr); Serial.print(" on channel " ); Serial.print(pairingData.channel); // channel used by the server So, now that we know the server details, we can call the addPeer() function and pass as arguments the server MAC address and channel to add the server to the peer list. addPeer(pairingData.macAddr, pairingData.channel); // add the server to the peer list If the pairing is successful, we change the pairingStatus to PAIR_PAIRED. pairingStatus = PAIR_PAIRED; // set the pairing status

Auto Pairing

The autoPairing() function returns the pairing status. PairingStatus autoPairing(){ We can have different scenarios. If it is of type PAIR_REQUEST, it will set up the ESP-NOW callback functions and send the first message of type PAIRING to the broadcast address on a predefined channel (starting at 1). After that, we change the pairing status to PAIR_REQUESTED (it means we've already sent a request). case PAIR_REQUEST: Serial.print("Pairing request on channel " ); Serial.println(channel); // set WiFi channel ESP_ERROR_CHECK(esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE)); if (esp_now_init() != ESP_OK) { Serial.println("Error initializing ESP-NOW"); } // set callback routines esp_now_register_send_cb(OnDataSent); esp_now_register_recv_cb(OnDataRecv); // set pairing data to send to the server pairingData.msgType = PAIRING; pairingData.id = BOARD_ID; pairingData.channel = channel; // add peer and send request addPeer(serverAddress, channel); esp_now_send(serverAddress, (uint8_t *) &pairingData, sizeof(pairingData)); previousMillis = millis(); pairingStatus = PAIR_REQUESTED; After sending a pairing message, we wait some time to see if we get a message from the server. If we don't, we try on the next Wi-Fi channel and change the pairingStatus to PAIR_REQUEST again, so that the board sends a new request on a different Wi-Fi channel. case PAIR_REQUESTED: // time out to allow receiving response from server currentMillis = millis(); if(currentMillis - previousMillis > 250) { previousMillis = currentMillis; // time out expired, try next channel channel ++; if (channel > MAX_CHANNEL){ channel = 1; } pairingStatus = PAIR_REQUEST; } break; If the pairingStatus is PAIR_PAIRED, meaning we're already paired with the server, we don't need to do anything. case PAIR_PAIRED: // nothing to do here break; Finally, return the pairingStatus. return pairingStatus;

setup()

In the setup(), set the pairingStatus to PAIR_REQUEST. pairingStatus = PAIR_REQUEST;

loop()

In the loop(), check if the board is paired with the server before doing anything else. if (autoPairing() == PAIR_PAIRED) { This will run the autoPairing() function and handle the auto-pairing with the server. When the board is paired with the sender (PAIR_PAIRED), we can communicate with the server to exchange data with messages of type DATA.

Sending Messages to the Server

In this case, we're sending arbitrary temperature and humidity values, but you can exchange any other data with the server. unsigned long currentMillis = millis(); if (currentMillis - previousMillis >= interval) { // Save the last time a new reading was published previousMillis = currentMillis; //Set values to send myData.msgType = DATA; myData.id = BOARD_ID; myData.temp = readDHTTemperature(); myData.hum = readDHTHumidity(); myData.readingId = readingId++; esp_err_t result = esp_now_send(serverAddress, (uint8_t *) &myData, sizeof(myData)); }

Testing the Sender Boards

Now, you can test the sender boards. We recommend opening a serial communication with the server on another software like PuTTY for example so that you can see what's going on on the server and sender simultaneously. After having the server running, you can upload the sender code to the other boards. After uploading the code, open the Serial Monitor at a baud rate of 115200 and press the RST button so that the board starts running the code. This is what the sender should return. As you can see, first, it sends a pairing request using different channels until it gets a response from the server. In this case, it is using channel 6. After that, we start receiving messages from the server. We also send messages to the server. On the server side, this is what happens: The server receives a pairing request from the sender. It will pair with the sender. In my case, it was already paired because I had run this code before. After data, we start sending and receiving data. You can upload the sender code to multiple boards and they will all automatically pair with the server. The sender boards can be ESP32 or ESP8266 boards. Make sure you use the right code for the board you're using. Now, you can go to the server's IP address to see the readings from the sender boards displayed on the dashboard. The web page is prepared to display readings from two boards. If you want to display more readings you need to modify the web page.

Wrapping Up

In this tutorial, we've shown you how you can build a ESP-NOW Web Server, pair with peers automatically and establish a two-way communication between server and senders boards. You can adapt the parts of code that deal with auto-pairing and use them in your ESP-NOW examples. We would like to thank Jean-Claude Servaye for sharing his ESP-NOW auto-pairing code sketches with us. We only made a few modifications to the sketches. You can find the original codes on his GitHub page. You may also like: More ESP-NOW examples Learn ESP32 with Arduino IDE ebook Home Automation using ESP8266 ebook Build Web Server with ESP32 and ESP8266 ebook Thanks for reading.
SMART HOME with Raspberry Pi ESP32 and ESP8266 Node-RED InfluxDB eBook

SMART HOME with Raspberry Pi, ESP32, ESP8266 [eBook]

Learn how to build a home automation system and we'll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD Learn how to build a home automation system and we'll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD

LILYGO T-SIM7000G ESP32: Get GPS Data (Latitude, Longitude, Altitude, and more)

In this quick guide, you'll learn how to get GPS data with the LILYGO T-SIM7000G ESP32 board using Arduino IDE. This tutorial is also compatible with a regular ESP32 connected to a SIM7000G module.

Introducing the LILYGO T-SIM7000G ESP32

The LILYGO T-SIM7000G is an ESP32 development board with a SIM7000G chip. This adds LTE (4G), GPS, and GPRS to your board. This means that with this board you can send SMS, get location and time using GPS, and connect it to the internet using a SIM card data plan. This board doesn't support phone calls. Besides the SIM7000G module, the board also comes with some interesting features like a battery holder for a 18650 battery, a battery charging circuit where you can connect solar panels to recharge the battery, and a microSD card slot that can be useful for data logging projects or to save configuration settings. For a more in-depth introduction, we recommend following the getting started guide: Getting Started with LILYGO T-SIM7000G ESP32 (LTE, GPRS, and GPS) Where to buy LILYGO T-SIM7000G ESP32? Check the following link: LILYGO T-SIM7000G ESP32 (LTE, GPRS, and GPS) Maker Advisor All stores in the previous link should sell the latest version, but double-check the product page, just in case the seller changes something. You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Libraries

The ESP32 communicates with the SIM7000G chip by sending AT commands via serial communication. You don't need a library, you can simply establish a serial communication with the module and start sending AT commands. There's a manual with all the SIM7000G AT commands: SIM7000G AT Commands Manual However, it might be more practical to use a library. For example, the TinyGSM library knows which commands to send, and how to handle AT responses, and wraps that into the standard Arduino Client interfacethat's the library we'll use in this tutorial.

Installing the TinyGSM Library

Open your Arduino IDE and go to Sketch > Include Library > Manage Libraries. The Library Manager should open. Search for TinyGSM. Select the TinyGSM library by Volodymyr Shymanskyy. You also need to install the StreamDebugger library. Go to Sketch > Include Library > Manage Libraries, search for StreamDebugger, and install it.

Preparing the LILYGO T-SIM7000G ESP32 Board

To get GPS data with your board, you don't need to connect a SIM card. You only need to connect the GPS antenna to the board.

LILYGO T-SIM7000G ESP32 BoardGet GPS Data

Copy the following code to your Arduino IDE. /* Rui Santos Complete project details at https://RandomNerdTutorials.com/lilygo-t-sim7000g-esp32-gps-data/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ #define TINY_GSM_MODEM_SIM7000 #define TINY_GSM_RX_BUFFER 1024 // Set RX buffer to 1Kb #include <TinyGsmClient.h> // LilyGO T-SIM7000G Pinout #define UART_BAUD 115200 #define PIN_DTR 25 #define PIN_TX 27 #define PIN_RX 26 #define PWR_PIN 4 #define LED_PIN 12 // Set serial for debug console (to Serial Monitor, default speed 115200) #define SerialMon Serial // Set serial for AT commands #define SerialAT Serial1 TinyGsm modem(SerialAT); void setup(){ SerialMon.begin(115200); SerialMon.println("Place your board outside to catch satelite signal"); // Set LED OFF pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, HIGH); //Turn on the modem pinMode(PWR_PIN, OUTPUT); digitalWrite(PWR_PIN, HIGH); delay(300); digitalWrite(PWR_PIN, LOW); delay(1000); // Set module baud rate and UART pins SerialAT.begin(UART_BAUD, SERIAL_8N1, PIN_RX, PIN_TX); // Restart takes quite some time // To skip it, call init() instead of restart() SerialMon.println("Initializing modem..."); if (!modem.restart()) { Serial.println("Failed to restart modem, attempting to continue without restarting"); } // Print modem info String modemName = modem.getModemName(); delay(500); SerialMon.println("Modem Name: " + modemName); String modemInfo = modem.getModemInfo(); delay(500); SerialMon.println("Modem Info: " + modemInfo); } void loop(){ // Set SIM7000G GPIO4 HIGH ,turn on GPS power // CMD:AT+SGPIO=0,4,1,1 // Only in version 20200415 is there a function to control GPS power modem.sendAT("+SGPIO=0,4,1,1"); if (modem.waitResponse(10000L) != 1) { SerialMon.println(" SGPIO=0,4,1,1 false "); } modem.enableGPS(); delay(15000); float lat = 0; float lon = 0; float speed = 0; float alt = 0; int vsat = 0; int usat = 0; float accuracy = 0; int year = 0; int month = 0; int day = 0; int hour = 0; int min = 0; int sec = 0; for (int8_t i = 15; i; i--) { SerialMon.println("Requesting current GPS/GNSS/GLONASS location"); if (modem.getGPS(&lat, &lon, &speed, &alt, &vsat, &usat, &accuracy, &year, &month, &day, &hour, &min, &sec)) { SerialMon.println("Latitude: " + String(lat, 8) + "\tLongitude: " + String(lon, 8)); SerialMon.println("Speed: " + String(speed) + "\tAltitude: " + String(alt)); SerialMon.println("Visible Satellites: " + String(vsat) + "\tUsed Satellites: " + String(usat)); SerialMon.println("Accuracy: " + String(accuracy)); SerialMon.println("Year: " + String(year) + "\tMonth: " + String(month) + "\tDay: " + String(day)); SerialMon.println("Hour: " + String(hour) + "\tMinute: " + String(min) + "\tSecond: " + String(sec)); break; } else { SerialMon.println("Couldn't get GPS/GNSS/GLONASS location, retrying in 15s."); delay(15000L); } } SerialMon.println("Retrieving GPS/GNSS/GLONASS location again as a string"); String gps_raw = modem.getGPSraw(); SerialMon.println("GPS/GNSS Based Location String: " + gps_raw); SerialMon.println("Disabling GPS"); modem.disableGPS(); // Set SIM7000G GPIO4 LOW ,turn off GPS power // CMD:AT+SGPIO=0,4,1,0 // Only in version 20200415 is there a function to control GPS power modem.sendAT("+SGPIO=0,4,1,0"); if (modem.waitResponse(10000L) != 1) { SerialMon.println(" SGPIO=0,4,1,0 false "); } delay(200); // Do nothing forevermore while (true) { modem.maintain(); } } View raw code

How the Code Works

Let's take a quick look at the relevant parts of the code. First, you need to define the module you're using. The library is compatible with many different modules. To use the SIM7000G, include the following line: #define TINY_GSM_MODEM_SIM7000 Include the TinyGSM library. #include <TinyGsmClient.h> The following lines set the board pins to control the modem: // LilyGO T-SIM7000G Pinout #define UART_BAUD 115200 #define PIN_DTR 25 #define PIN_TX 27 #define PIN_RX 26 #define PWR_PIN 4 #define LED_PIN 12 You need to create two Serial instances. One for the Serial Monitor that we'll call SerialMon, and another to communicate with the modem via AT commands, that we call SerialAT. // Set serial for debug console (to Serial Monitor, default speed 115200) #define SerialMon Serial // Set serial for AT commands #define SerialAT Serial1 Create a TinyGSM instance called modem on the SerialAT. TinyGsm modem(SerialAT); Initialize the Serial Monitor at a baud rate of 115200. SerialMon.begin(115200); Turn on the modem by setting the power pin to HIGH and LOW at a specific interval. //Turn on the modem pinMode(PWR_PIN, OUTPUT); digitalWrite(PWR_PIN, HIGH); delay(300); digitalWrite(PWR_PIN, LOW); Initialize a Serial communication with the modem on the RX and TX pins we defined earlier. SerialAT.begin(UART_BAUD, SERIAL_8N1, PIN_RX, PIN_TX); Restart or init the modem: // Restart takes quite some time // To skip it, call init() instead of restart() SerialMon.println("Initializing modem..."); if (!modem.restart()) { Serial.println("Failed to restart modem, attempting to continue without restarting"); } Get some modem info using the getModemName() and getModemInfo() methods. These lines are optional and you don't actually need them to get GPS data. // Print modem info String modemName = modem.getModemName(); delay(500); SerialMon.println("Modem Name: " + modemName); String modemInfo = modem.getModemInfo(); delay(500); SerialMon.println("Modem Info: " + modemInfo); There are two versions of the LILYGO SIM7000G ESP32 board. The latest comes with active GPS antenna power controlwhen the module GPIO 4 is not turned on the antenna consumes only the static current of the LDO. This means we need to turn GPIO 4 on before getting GPS data to power the antenna. That's what the next lines do: // Set SIM7000G GPIO4 HIGH ,turn on GPS power // CMD:AT+SGPIO=0,4,1,1 // Only in version 20200415 is there a function to control GPS power modem.sendAT("+SGPIO=0,4,1,1"); if (modem.waitResponse(10000L) != 1) { SerialMon.println(" SGPIO=0,4,1,1 false "); } You can start GPS using the enableGPS() method. modem.enableGPS(); Then, we create variables where we'll save the GPS data. We'll get latitude, longitude, speed, altitude, visible satellites, used satellites, accuracy, and date and time. delay(15000); float lat = 0; float lon = 0; float speed = 0; float alt = 0; int vsat = 0; int usat = 0; float accuracy = 0; int year = 0; int month = 0; int day = 0; int hour = 0; int min = 0; int sec = 0; The following line gets GPS data using the getGPS() method and saves the values on the right variables. if (modem.getGPS(&lat, &lon, &speed, &alt, &vsat, &usat, &accuracy, &year, &month, &day, &hour, &min, &sec)) Then, we simply print the values on the Serial Monitor. Now that you have the relevant information saved on variables, it's easy to modify this project for your own needs. For example, a GPS tracker, GPS data logger, etc. SerialMon.println("Latitude: " + String(lat, 8) + "\tLongitude: " + String(lon, 8)); SerialMon.println("Speed: " + String(speed) + "\tAltitude: " + String(alt)); SerialMon.println("Visible Satellites: " + String(vsat) + "\tUsed Satellites: " + String(usat)); SerialMon.println("Accuracy: " + String(accuracy)); SerialMon.println("Year: " + String(year) + "\tMonth: " + String(month) + "\tDay: " + String(day)); SerialMon.println("Hour: " + String(hour) + "\tMinute: " + String(min) + "\tSecond: " + String(sec)); You can also get all raw data returned by the GPS using the getGPRSraw() method. String gps_raw = modem.getGPSraw(); SerialMon.println("GPS/GNSS Based Location String: " + gps_raw); When you're done using GPS, you can turn it off using the disableGPS() method: modem.disableGPS(); And finally, turn off the power to the antenna by turning GPIO 4 off: // Set SIM7000G GPIO4 LOW ,turn off GPS power // CMD:AT+SGPIO=0,4,1,0 // Only in version 20200415 is there a function to control GPS power modem.sendAT("+SGPIO=0,4,1,0"); if (modem.waitResponse(10000L) != 1) { SerialMon.println(" SGPIO=0,4,1,0 false "); }

Demonstration

In your Arduino IDE, go to Tools > Boards and select the ESP32 Dev Module. Select the COM port in Tools > Port. Then, upload the code to your board. Open the Serial Monitor at a baud rate of 115200 and press the on-board RST button to restart the board. Place your board outside or next to a window or door so that it can catch satellite signals. It may take some time until it is able to get some GPS data, as you can see in the screenshot of my Serial Monitor. As you can see, it gets latitude, longitude, speed, altitude, visible satellites, number of used satellites to get position, accuracy, and UTC date and time. The longitude and latitude I got were very accurate. So, in my case, it was working pretty well to get the location. It also outputs the complete GNSS navigation information parsed from NMEA sentences (that you can't see above because the Serial Monitor window is too small). NMEA stands for National Marine Electronics Association, and in the world of GPS, it is a standard data format supported by GPS manufacturers. The output is as follows. The commas separate different values. 1,1,20220809173458.000,41.12XXXX,-8.52XXXX,140.200,0.00,237.6,1,,2.3,2.5,1.0,,20,5,1,,48,, Here's what each value means, in order:
    GNSS run status Fix status UTC date and time Latitude Longitude MSL altitude Speed over ground Course over ground Fix mode Reserver1 HDOP PDOP VDOP Reserved2 GNSS Satellites in View GPS Satellites used GLONASS Satellites used Reserver3 C/N0 max HPA VPA
You can learn more about these parameters and possible values by checking the AT+CGNSINF AT command on the SIM7000G AT commands manual.

Wrapping Up

In this tutorial, you learned how to use the LILYGO T-SIM7000G ESP32 board to get GPS data. We showed you a simple example that prints the GPS data in the Serial Monitor. The idea is to modify the example and apply it to your own projects. It should also be compatible with a regular ESP32 board connected to a separated SIM7000G module. The ESP32 T-SIM7000G board features will allow you to build a wide variety of projects taking into account that it can connect to the internet in remote locations using a SIM card data plan and send SMS. The fact that it can use a battery and solar panels for charging is also great, and the microSD card can also be very useful for datalogging or to save configuration settings.

Getting Started with LILYGO T-SIM7000G ESP32 (LTE, GPRS, and GPS)

Get started with the ESP32 and the SIM7000G LTE/GPS/GPRS module. Throughout this tutorial, we'll use the LILYGO T-SIM7000G ESP32 board that combines the ESP32 chip, the SIM7000G module, microSD card slot, battery holder, and charger on the same board. Besides Wi-Fi and Bluetooth, you can communicate with this ESP32 board using SMS. You can also connect it to the internet using your SIM card data plan and get GPS location.

Introducing the LILYGO T-SIM7000G ESP32

The LILYGO T-SIM7000G is an ESP32 development board with a SIM7000G chip. This adds LTE (4G), GPS, and GPRS to your board. This means that with this board you can send SMS, get location and time using GPS, and connect it to the internet using a SIM card data plan. This board doesn't support phone calls. Besides the SIM7000G module, the board also comes with some interesting features like a battery holder for a 18650 battery, a battery charging circuit where you can connect solar panels to recharge the battery, and a microSD card slot that can be useful for data logging projects or to save configuration settings. Here's a summary of the LILYGO T-SIM7000G ESP32 board features: Supply voltage: 3.3V DC or 5V DC ESP32 chip (WROVER-B Module) (240MHz dual-core processor) Flash memory: 4MB PSRAM: 8MB SRAM: 520KB Built-in Wi-Fi Built-in Bluetooth USB to serial converter: CP2104 or CH9102 (drivers) Built-in SIM7000G module Built-in nano SIM card slot Built-in SIM antenna slot Built-in GPS antenna slot Built-in Li-ion/Li-Po battery charging circuit: DW01A battery protection IC CN3065 solar energy charging interface for 4.4-6.8V solar panel Built-in 1x 18650 battery holder Built-in solar panel connector 2p JST-PH Built-in Micro SD card slot Built-in on/off switch To use the capabilities of this board you need to have a nano SIM card with a data plan and a USB-C cable to upload code to the board. The package includes an external antenna for LTE, and another antenna for GPS. There are two versions of this board (Version 20191227 and version 20200415). The picture below shows the two versions. Visually, they mainly differ on the position of the nano SIM card holder. The first version had some design issues, so it is recommended to get the latest version. Aditionally, the latest version comes with some improvements taking into account users' feedback. I got my board a long time ago, I've got the first version and that's the one we'll use throughout this tutorial. However, this is also compatible with the latest board. Here's a list of the improvements on the latest version (check the documentation): Added active GPS antenna power control, when the module GPIO 4 is not turned on, the antenna consumes only the static current of the LDO; Replaced TP4056 with CN3065 for solar charge input management; Added reverse battery protection; Added battery overcharge protection; Added battery over-discharge protection. You can check the schematic diagrams for each version on the following links: LILYGO T-SIM7000G ESP32 Version 1.0 schematic diagram LILYGO T-SIM7000G ESP32 Version 1.1 schematic diagram Where to buy LILYGO T-SIM7000G ESP32? Check the following link: LILYGO T-SIM7000G ESP32 (LTE, GPRS, and GPS) Maker Advisor All stores in the previous link should sell the latest version, but double-check the product page, just in case the seller changes something. You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

LILYGO T-SIM7000G ESP32 Pinout

The following pictures show the pinout of the T-SIM7000G ESP32 board. This is the pinout for version V1.0.
Image source
And this is the pinout for the improved board V1.1.
Image source
The following table shows the connections between the ESP32 and the SIM7000G chip:
SIM7000G ESP32
TX GPIO 26
RX GPIO 27
POWER GPIO 4
To communicate with the microSD card, you need SPI communication protocol. These are the GPIOs used:
MicroSD Card (TF card) ESP32
MOSI GPIO 15
SCLK GPIO 14
CS GPIO 13
MISO GPIO 2

SIM Card

This board only supports nano SIM cards. You need a SIM card for LTE and GPRS. However, if you only want to use GPS data, you don't need a SIM card. To use LTE and GPRS you need a SIM card with some data plan. This can be expensive in some countries, so it might be cost prohibitive depending on how much you can get a data plan for in your country. Where we live (Portugal), we can get a SIM card with data plan, calls, and SMS (enough for ESP32 projects) for approximately $12. We recommend using a SIM card with a prepaid or monthly plan, so that you know exactly how much you'll spend. There are also companies specialized in SIM cards for IoT projects.

APN Details

To connect your SIM card to the internet, you need to have your phone plan provider's APN details. You need the domain name, username, and password. In my case, I'm using Vodafone Portugal. If you search for GPRS APN settings followed by your phone plan provider name, (in my case its: GPRS APN Vodafone Portugal), you can usually find in a forum or in their website all the information that you need. It might be a bit tricky to find the details if you don't use a well-known provider. So, you might need to contact them directly.

AT Commands

AT commands are used to control MODEMs, as is the case of the SIM7000G. In the case of the ESP32, you send the AT commands via serial communication protocol. Then, the modem responds back also via serial communication. There are four types of AT commands: test; read; set; execution. You can find the complete list of AT commands for the SIM7000G on the following link: SIM7000G AT Commands (mudar para um link nosso?) Here are some of the most common AT commands: check communication with the module: AT check if SIM card is ready: AT+CPIN? check the registration status of the device: AT+CGREG? send SMS to a number: AT+CMGS=PHONE_NUMBER(in international format)

Libraries

As we explained previously, the ESP32 communicates with the SIM7000G chip by sending AT commands via serial communication. You don't need a library, you can simply establish a serial communication with the module and start sending AT commands. However, it might be more practical to use a library. For example, the TinyGSM library knows which commands to send, and how to handle AT responses, and wraps that into the standard Arduino Client interfacethat's the library we'll use in this tutorial.

Installing the TinyGSM Library

Open your Arduino IDE and go to Sketch > Include Library > Manage Libraries. The Library Manager should open. Search for TinyGSM. Select the TinyGSM library by Volodymyr Shymanskyy. You also need to install the StreamDebugger library. Go to Sketch > Include Library > Manage Libraries, search for StreamDebugger, and install it.

Preparing the LILYGO T-SIM7000G ESP32 Board

Before testing your board, you need to follow the next steps: 1) Insert a nano SIM card; 2) Connect the Full Band LTE antenna (SIM); 3) Connect the GPS antenna. If you want to test the microSD card features, you should only connect a microSD card, after uploading the code.

LILYGO T-SIM7000G ESP32 Network Test

The first sketch you should run on your board is the Network Test. This will tell you the network selection settings you should usethis depends on the SIM card, modem(SIM7000G), and the mobile network operator it uses.
    Copy the following code to your Arduino IDE (the code was adapted from this example).
/* Rui Santos Complete project details at https://RandomNerdTutorials.com/lilygo-t-sim7000g-esp32-lte-gprs-gps/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ // Original code: https://github.com/Xinyuan-LilyGO/LilyGO-T-SIM7000G/blob/master/examples/Arduino_NetworkTest/Arduino_NetworkTest.ino #define TINY_GSM_MODEM_SIM7000 #define TINY_GSM_RX_BUFFER 1024 // Set RX buffer to 1Kb #define SerialAT Serial1 // Set serial for debug console (to the Serial Monitor, default speed 115200) #define SerialMon Serial // See all AT commands, if wanted // #define DUMP_AT_COMMANDS // set GSM PIN, if any #define GSM_PIN "" // Your GPRS credentials, if any const char apn[] = ""; //SET TO YOUR APN const char gprsUser[] = ""; const char gprsPass[] = ""; #include <TinyGsmClient.h> #include <SPI.h> #include <SD.h> #include <Ticker.h> #ifdef DUMP_AT_COMMANDS #include <StreamDebugger.h> StreamDebugger debugger(SerialAT, SerialMon); TinyGsm modem(debugger); #else TinyGsm modem(SerialAT); #endif // LilyGO T-SIM7000G Pinout #define UART_BAUD 115200 #define PIN_DTR 25 #define PIN_TX 27 #define PIN_RX 26 #define PWR_PIN 4 #define SD_MISO 2 #define SD_MOSI 15 #define SD_SCLK 14 #define SD_CS 13 #define LED_PIN 12 void modemPowerOn(){ pinMode(PWR_PIN, OUTPUT); digitalWrite(PWR_PIN, LOW); delay(1000); digitalWrite(PWR_PIN, HIGH); } void modemPowerOff(){ pinMode(PWR_PIN, OUTPUT); digitalWrite(PWR_PIN, LOW); delay(1500); digitalWrite(PWR_PIN, HIGH); } void modemRestart(){ modemPowerOff(); delay(1000); modemPowerOn(); } void setup(){ // Set console baud rate SerialMon.begin(115200); delay(10); // Set LED OFF pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, HIGH); modemPowerOn(); Serial.println("========SDCard Detect.======"); SPI.begin(SD_SCLK, SD_MISO, SD_MOSI); if (!SD.begin(SD_CS)) { Serial.println("SDCard MOUNT FAIL"); } else { uint32_t cardSize = SD.cardSize() / (1024 * 1024); String str = "SDCard Size: " + String(cardSize) + "MB"; Serial.println(str); } Serial.println("==========================="); SerialAT.begin(UART_BAUD, SERIAL_8N1, PIN_RX, PIN_TX); Serial.println("/**********************************************************/"); Serial.println("To initialize the network test, please make sure your LTE "); Serial.println("antenna has been connected to the SIM interface on the board."); Serial.println("/**********************************************************/\n\n"); delay(10000); } void loop(){ String res; Serial.println("========INIT========"); if (!modem.init()) { modemRestart(); delay(2000); Serial.println("Failed to restart modem, attempting to continue without restarting"); return; } Serial.println("========SIMCOMATI======"); modem.sendAT("+SIMCOMATI"); modem.waitResponse(1000L, res); res.replace(GSM_NL "OK" GSM_NL, ""); Serial.println(res); res = ""; Serial.println("======================="); Serial.println("=====Preferred mode selection====="); modem.sendAT("+CNMP?"); if (modem.waitResponse(1000L, res) == 1) { res.replace(GSM_NL "OK" GSM_NL, ""); Serial.println(res); } res = ""; Serial.println("======================="); Serial.println("=====Preferred selection between CAT-M and NB-IoT====="); modem.sendAT("+CMNB?"); if (modem.waitResponse(1000L, res) == 1) { res.replace(GSM_NL "OK" GSM_NL, ""); Serial.println(res); } res = ""; Serial.println("======================="); String name = modem.getModemName(); Serial.println("Modem Name: " + name); String modemInfo = modem.getModemInfo(); Serial.println("Modem Info: " + modemInfo); // Unlock your SIM card with a PIN if needed if ( GSM_PIN && modem.getSimStatus() != 3 ) { modem.simUnlock(GSM_PIN); } for (int i = 0; i <= 4; i++) { uint8_t network[] = { 2, /*Automatic*/ 13, /*GSM only*/ 38, /*LTE only*/ 51 /*GSM and LTE only*/ }; Serial.printf("Try %d method\n", network[i]); modem.setNetworkMode(network[i]); delay(3000); bool isConnected = false; int tryCount = 60; while (tryCount--) { int16_t signal = modem.getSignalQuality(); Serial.print("Signal: "); Serial.print(signal); Serial.print(" "); Serial.print("isNetworkConnected: "); isConnected = modem.isNetworkConnected(); Serial.println( isConnected ? "CONNECT" : "NO CONNECT"); if (isConnected) { break; } delay(1000); digitalWrite(LED_PIN, !digitalRead(LED_PIN)); } if (isConnected) { break; } } digitalWrite(LED_PIN, HIGH); Serial.println(); Serial.println("Device is connected ."); Serial.println(); Serial.println("=====Inquiring UE system information====="); modem.sendAT("+CPSI?"); if (modem.waitResponse(1000L, res) == 1) { res.replace(GSM_NL "OK" GSM_NL, ""); Serial.println(res); } Serial.println("/**********************************************************/"); Serial.println("After the network test is complete, please enter the "); Serial.println("AT command in the serial terminal."); Serial.println("/**********************************************************/\n\n"); while (1) { while (SerialAT.available()) { SerialMon.write(SerialAT.read()); } while (SerialMon.available()) { SerialAT.write(SerialMon.read()); } } } View raw code
    Insert your SIM card pin, if you have it. In my case, I disabled the pin.
#define GSM_PIN ""
    Insert your apn details on the following lines:
const char apn[] = ""; //SET TO YOUR APN const char gprsUser[] = ""; const char gprsPass[] = ""; For example, in my case: const char apn[] = "net2.vodafone.pt"; //SET TO YOUR APN const char gprsUser[] = "vodafone"; const char gprsPass[] = "vodafone";
    Go to Tools > Board and select ESP32 Dev Module.
    Finally, upload the code to your board.
Now, you can insert a microSD card, if you want to test the microSD card features. Then, open the Serial Monitor at a baud rate of 115200. Press the on-board RST button to restart the board. Wait some time until the board connects to the network (in my case, it may take up to 2 minutes). You should get something similar in the Serial Monitor. You can see that it identifies the microSD card and connects to the network successfully. Check the preferred mode selection and the preferred selection between CAT-M and NB-IoT. You'll need those parameters later, and they differ depending on your SIM card and provider.

LILYGO T-SIM7000G ESP32: Connect to the Internet, Send SMS, and Get GPS Data

If everything went as expected, now you're ready to test other functions like connecting to the internet, sending SMS, and getting GPS data. Copy the following code to your Arduino IDE. This code was adapted from this example. /* Rui Santos Complete project details at https://RandomNerdTutorials.com/lilygo-t-sim7000g-esp32-lte-gprs-gps/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ // Based on the following example: https://github.com/Xinyuan-LilyGO/LilyGO-T-SIM7000G/blob/master/examples/Arduino_TinyGSM/AllFunctions/AllFunctions.ino #define TINY_GSM_MODEM_SIM7000 #define TINY_GSM_RX_BUFFER 1024 // Set RX buffer to 1Kb #define SerialAT Serial1 // See all AT commands, if wanted #define DUMP_AT_COMMANDS // set GSM PIN, if any #define GSM_PIN "" // Your GPRS credentials, if any const char apn[] = ""; //SET TO YOUR APN const char gprsUser[] = ""; const char gprsPass[] = ""; // Set phone number, if you want to test SMS // Set a recipient phone number to test sending SMS (it must be in international format including the "+" sign) #define SMS_TARGET "" #include <TinyGsmClient.h> #include <SPI.h> #include <SD.h> #include <Ticker.h> #ifdef DUMP_AT_COMMANDS // if enabled it requires the streamDebugger lib #include <StreamDebugger.h> StreamDebugger debugger(SerialAT, Serial); TinyGsm modem(debugger); #else TinyGsm modem(SerialAT); #endif #define uS_TO_S_FACTOR 1000000ULL // Conversion factor for micro seconds to seconds #define TIME_TO_SLEEP 60 // Time ESP32 will go to sleep (in seconds) #define UART_BAUD 115200 #define PIN_DTR 25 #define PIN_TX 27 #define PIN_RX 26 #define PWR_PIN 4 #define SD_MISO 2 #define SD_MOSI 15 #define SD_SCLK 14 #define SD_CS 13 #define LED_PIN 12 int counter, lastIndex, numberOfPieces = 24; String pieces[24], input; void setup(){ // Set console baud rate Serial.begin(115200); delay(10); // Set LED OFF pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, HIGH); pinMode(PWR_PIN, OUTPUT); digitalWrite(PWR_PIN, HIGH); delay(300); digitalWrite(PWR_PIN, LOW); SPI.begin(SD_SCLK, SD_MISO, SD_MOSI, SD_CS); if (!SD.begin(SD_CS)) { Serial.println("SDCard MOUNT FAIL"); } else { uint32_t cardSize = SD.cardSize() / (1024 * 1024); String str = "SDCard Size: " + String(cardSize) + "MB"; Serial.println(str); } Serial.println("\nWait..."); delay(1000); SerialAT.begin(UART_BAUD, SERIAL_8N1, PIN_RX, PIN_TX); // Restart takes quite some time // To skip it, call init() instead of restart() Serial.println("Initializing modem..."); if (!modem.restart()) { Serial.println("Failed to restart modem, attempting to continue without restarting"); } } void loop(){ // Restart takes quite some time // To skip it, call init() instead of restart() Serial.println("Initializing modem..."); if (!modem.init()) { Serial.println("Failed to restart modem, attempting to continue without restarting"); } String name = modem.getModemName(); delay(500); Serial.println("Modem Name: " + name); String modemInfo = modem.getModemInfo(); delay(500); Serial.println("Modem Info: " + modemInfo); // Unlock your SIM card with a PIN if needed if ( GSM_PIN && modem.getSimStatus() != 3 ) { modem.simUnlock(GSM_PIN); } modem.sendAT("+CFUN=0 "); if (modem.waitResponse(10000L) != 1) { DBG(" +CFUN=0 false "); } delay(200); /* 2 Automatic 13 GSM only 38 LTE only 51 GSM and LTE only * * * */ String res; // CHANGE NETWORK MODE, IF NEEDED res = modem.setNetworkMode(2); if (res != "1") { DBG("setNetworkMode false "); return ; } delay(200); /* 1 CAT-M 2 NB-Iot 3 CAT-M and NB-IoT * * */ // CHANGE PREFERRED MODE, IF NEEDED res = modem.setPreferredMode(1); if (res != "1") { DBG("setPreferredMode false "); return ; } delay(200); /*AT+CBANDCFG=<mode>,<band>[,<band>] * <mode> "CAT-M" "NB-IOT" * <band> The value of <band> must is in the band list of getting from AT+CBANDCFG=? * For example, my SIM card carrier "NB-iot" supports B8. I will configure +CBANDCFG= "Nb-iot ",8 */ /* modem.sendAT("+CBANDCFG=\"NB-IOT\",8 ");*/ /* if (modem.waitResponse(10000L) != 1) { DBG(" +CBANDCFG=\"NB-IOT\" "); }*/ delay(200); modem.sendAT("+CFUN=1 "); if (modem.waitResponse(10000L) != 1) { DBG(" +CFUN=1 false "); } delay(200); SerialAT.println("AT+CGDCONT?"); delay(500); if (SerialAT.available()) { input = SerialAT.readString(); for (int i = 0; i < input.length(); i++) { if (input.substring(i, i + 1) == "\n") { pieces[counter] = input.substring(lastIndex, i); lastIndex = i + 1; counter++; } if (i == input.length() - 1) { pieces[counter] = input.substring(lastIndex, i); } } // Reset for reuse input = ""; counter = 0; lastIndex = 0; for ( int y = 0; y < numberOfPieces; y++) { for ( int x = 0; x < pieces[y].length(); x++) { char c = pieces[y][x]; //gets one byte from buffer if (c == ',') { if (input.indexOf(": ") >= 0) { String data = input.substring((input.indexOf(": ") + 1)); if ( data.toInt() > 0 && data.toInt() < 25) { modem.sendAT("+CGDCONT=" + String(data.toInt()) + ",\"IP\",\"" + String(apn) + "\",\"0.0.0.0\",0,0,0,0"); } input = ""; break; } // Reset for reuse input = ""; } else { input += c; } } } } else { Serial.println("Failed to get PDP!"); } Serial.println("\n\n\nWaiting for network..."); if (!modem.waitForNetwork()) { delay(10000); return; } if (modem.isNetworkConnected()) { Serial.println("Network connected"); } // --------TESTING GPRS-------- Serial.println("\n---Starting GPRS TEST---\n"); Serial.println("Connecting to: " + String(apn)); if (!modem.gprsConnect(apn, gprsUser, gprsPass)) { delay(10000); return; } Serial.print("GPRS status: "); if (modem.isGprsConnected()) { Serial.println("connected"); } else { Serial.println("not connected"); } String ccid = modem.getSimCCID(); Serial.println("CCID: " + ccid); String imei = modem.getIMEI(); Serial.println("IMEI: " + imei); String cop = modem.getOperator(); Serial.println("Operator: " + cop); IPAddress local = modem.localIP(); Serial.println("Local IP: " + String(local)); int csq = modem.getSignalQuality(); Serial.println("Signal quality: " + String(csq)); SerialAT.println("AT+CPSI?"); //Get connection type and band delay(500); if (SerialAT.available()) { String r = SerialAT.readString(); Serial.println(r); } Serial.println("\n---End of GPRS TEST---\n"); modem.gprsDisconnect(); if (!modem.isGprsConnected()) { Serial.println("GPRS disconnected"); } else { Serial.println("GPRS disconnect: Failed."); } // --------TESTING GPS-------- Serial.println("\n---Starting GPS TEST---\n"); // Set SIM7000G GPIO4 HIGH ,turn on GPS power // CMD:AT+SGPIO=0,4,1,1 // Only in version 20200415 is there a function to control GPS power modem.sendAT("+SGPIO=0,4,1,1"); if (modem.waitResponse(10000L) != 1) { DBG(" SGPIO=0,4,1,1 false "); } modem.enableGPS(); float lat, lon; while (1) { if (modem.getGPS(&lat, &lon)) { Serial.printf("lat:%f lon:%f\n", lat, lon); break; } else { Serial.print("getGPS "); Serial.println(millis()); } delay(2000); } modem.disableGPS(); // Set SIM7000G GPIO4 LOW ,turn off GPS power // CMD:AT+SGPIO=0,4,1,0 // Only in version 20200415 is there a function to control GPS power modem.sendAT("+SGPIO=0,4,1,0"); if (modem.waitResponse(10000L) != 1) { DBG(" SGPIO=0,4,1,0 false "); } Serial.println("\n---End of GPRS TEST---\n"); // --------TESTING SENDING SMS-------- res = modem.sendSMS(SMS_TARGET, String("Hello from ") + imei); DBG("SMS:", res ? "OK" : "fail"); // --------TESTING POWER DONW-------- // Try to power-off (modem may decide to restart automatically) // To turn off modem completely, please use Reset/Enable pins modem.sendAT("+CPOWD=1"); if (modem.waitResponse(10000L) != 1) { DBG("+CPOWD=1"); } // The following command does the same as the previous lines modem.poweroff(); Serial.println("Poweroff."); // GO TO SLEEP esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR); delay(200); esp_deep_sleep_start(); // Do nothing forevermore while (true) { modem.maintain(); } } View raw code Insert your SIM card pin on the following line: #define GSM_PIN "" Fill your APN details: const char apn[] = "net2.vodafone.pt"; //SET TO YOUR APN const char gprsUser[] = "vodafone"; const char gprsPass[] = "vodafone"; Set a recipient phone number to test sending SMS (it must be in international format including the + sign): // Set phone number, if you want to test SMS #define SMS_TARGET "+XXXXXXXXXXXXXXXX" Set the network mode with the value you got from the previous example. /* 2 Automatic 13 GSM only 38 LTE only 51 GSM and LTE only * * * */ String res; // CHANGE NETWORK MODE, IF NEEDED res = modem.setNetworkMode(2); if (res != "1") { DBG("setNetworkMode false "); return ; } delay(200); Change the preferred mode with the value you got from the previous example. /* 1 CAT-M 2 NB-Iot 3 CAT-M and NB-IoT * * */ // CHANGE PREFERRED MODE, IF NEEDED res = modem.setPreferredMode(1); if (res != "1") { DBG("setPreferredMode false "); return ; } delay(200); After that, you can upload the code to your board. Don't forget to select ESP32 Dev Module in Tools > Board. You also need to remove the microSD card every time you want to upload a new sketch. After uploading, you can insert the microSD card. Open the Serial Monitor at a baud rate of 115200, and press the on-board RST button to restart it. The board may take some time to get GPS data for the first time. Your board needs to be placed outside to be able to get a satellite signal. I placed mine next to the window and it was able to accurately get the GPS position. You should get something similar. Wait... Initializing modem... ATE0 AT+CFUN=0 Failed to restart modem, attempting to continue without restarting Initializing modem... AT AT AT OK ATE0 ATE0 OK AT+CMEE=0 OK AT+CLTS=1 OK AT+CBATCHK=1 OK AT+CPIN? +CPIN: READY OK AT+GMM SIMCOM_SIM7000G OK Modem Name: SIMCOM SIM7000G ATI SIM7000G R1529 OK Modem Info: SIM7000G R1529 AT+SGPIO=0,4,1,0 OK AT+CPIN? +CPIN: READY OK AT+CFUN=0 +CPIN: NOT READY OK AT+CNMP=2 OK AT+CMNB=1 OK AT+CFUN=1 OK AT+CGDCONT=1,"IP","net2.vodafone.pt","0.0.0.0",0,0,0,0 AT+CGDCONT=13,"IP","net2.vodafone.pt","0.0.0.0",0,0,0,0 Waiting for network... AT+CEREG? OK AT+CGREG? +CGREG: 0,2 OK AT+CEREG? +CEREG: 0,0 OK AT+CGREG? +CGREG: 0,2 OK AT+CEREG? +CEREG: 0,0 OK AT+CGREG? +CGREG: 0,2 OK AT+CEREG? +CEREG: 0,0 OK AT+CGREG? +CGREG: 0,2 OK AT+CEREG? +CEREG: 0,0 OK AT+CGREG? +CGREG: 0,2 OK AT+CEREG? +CEREG: 0,0 OK AT+CGREG? +CGREG: 0,2 OK AT+CEREG? DST: 1 *PSUTTZ: 22/08/07,13:45:17","+04",1 +CEREG: 0,0 OK AT+CGREG? +CGREG: 0,1 OK AT+CEREG? +CEREG: 0,0 OK AT+CGREG? +CGREG: 0,1 OK Network connected ---Starting GPRS TEST--- Connecting to: net2.vodafone.pt AT+CIPSHUT SHUT OK AT+CGATT=0 OK AT+SAPBR=3,1,"Contype","GPRS" OK AT+SAPBR=3,1,"APN","net2.vodafone.pt" OK AT+SAPBR=3,1,"USER","vodafone" OK AT+SAPBR=3,1,"PWD","vodafone" OK AT+CGDCONT=1,"IP","net2.vodafone.pt" OK AT+CGATT=1 OK AT+CGACT=1,1 DST: 1 *PSUTTZ: 22/08/07,13:45:19","+04",1 OK AT+SAPBR=1,1 OK AT+SAPBR=2,1 +SAPBR: 1,1,"10.196.118.208" OK AT+CIPMUX=1 OK AT+CIPQSEND=1 OK AT+CIPRXGET=1 OK AT+CSTT="net2.vodafone.pt","vodafone","vodafone" OK AT+CIICR OK AT+CIFSR;E0 10.196.118.208 OK GPRS status: AT+CGATT? +CGATT: 1 OK AT+CIFSR;E0 10.196.118.208 OK connected AT+CCID 8935101211825295132f OK CCID: 8935101211825295132f AT+GSN 869951031125929 OK IMEI: 869951031125929 AT+COPS? +COPS: 0,0,"vodafone P",3 OK Operator: vodafone P AT+CIFSR;E0 10.196.118.208 OK Local IP: 3497444362 AT+CSQ +CSQ: 22,0 OK Signal quality: 22 +CPSI: GSM,Online,268-01,0x000e,63308,15 EGSM 900,-73,0,38-38 OK ---End of GPRS TEST--- AT+CIPSHUT SHUT OK AT+CGATT=0 +SAPBR 1: DEACT OK AT+CGATT? +CGATT: 0 OK GPRS disconnected ---Starting GPS TEST--- AT+SGPIO=0,4,1,1 OK AT+CGNSPWR=1 OK AT+CGNSINF +CGNSINF: 0,,,,,,,,,,,,,,,,,,,, OK getGPS 26839 AT+CGNSINF +CGNSINF: 1,0,,,,,,,0,,,,,,12,,,,50,, OK getGPS 28844 AT+CGNSINF +CGNSINF: 1,0,,,,,,,0,,,,,,12,,,,51,, OK getGPS 30850 AT+CGNSINF +CGNSINF: 1,0,,,,,,,0,,,,,,13,,,,51,, OK getGPS 32856 AT+CGNSINF +CGNSINF: 1,0,,,,,,,0,,,,,,12,,,,51,, OK getGPS 34862 AT+CGNSINF +CGNSINF: 1,0,,,,,,,0,,,,,,12,,,,51,, OK getGPS 36868 AT+CGNSINF +CGNSINF: 1,1,20220807134533.000,41.12XXXX,-8.530XXXX,116.200,0.00,0.0,1,,4.2,,,,13,4,,,51,, OK lat:41.12XXXX lon:-8.530XXXX AT+CGNSPWR=0 OK AT+SGPIO=0,4,1,0 OK ---End of GPRS TEST--- AT+CMGF=1 OK AT+CSCS="GSM" OK AT+CMGS="+351916XXXXXXXX" >Hello from 86995103XXXXXXXXX +CMGS: 228 OK AT+CPOWD=1 NORMAL POWER DOWN AT+CPOWD=1 Poweroff.

How the Code Works

Let's take a quick look at the relevant parts of code. First, you need to define the module you're using. The library is compatible with many different modules. To use the SIM7000G, include the following line: #define TINY_GSM_MODEM_SIM7000 Insert the SIM card pin, APN details, and SMS recipient: // set GSM PIN, if any #define GSM_PIN "" // Your GPRS credentials, if any const char apn[] = "net2.vodafone.pt"; //SET TO YOUR APN const char gprsUser[] = "vodafone"; const char gprsPass[] = "vodafone"; // Set phone numbers, if you want to test SMS #define SMS_TARGET "+351916301581" Include the TinyGSM and SPI libraries. You also need to include the SD library if you want to use the microSD card. #include <TinyGsmClient.h> #include <SPI.h> #include <SD.h> #include <Ticker.h> Create a TinyGsmClient instance: #ifdef DUMP_AT_COMMANDS // if enabled it requires the streamDebugger lib #include <StreamDebugger.h> StreamDebugger debugger(SerialAT, Serial); TinyGsm modem(debugger); #else TinyGsm modem(SerialAT); #endif

SIM7000G pinout

The following lines set the module baud rate and pinout: #define UART_BAUD 115200 #define PIN_DTR 25 #define PIN_TX 27 #define PIN_RX 26 #define PWR_PIN 4 #define SD_MISO 2 #define SD_MOSI 15 #define SD_SCLK 14 #define SD_CS 13 #define LED_PIN 12

Power the modem

In the setup(), you always need to include the following instructions to turn on the modem: pinMode(PWR_PIN, OUTPUT); digitalWrite(PWR_PIN, HIGH); delay(300); digitalWrite(PWR_PIN, LOW);

Initialize microSD Card

The following lines initialize the microSD card on the pins we defined earlier. SPI.begin(SD_SCLK, SD_MISO, SD_MOSI, SD_CS); if (!SD.begin(SD_CS)) { Serial.println("SDCard MOUNT FAIL"); } else { uint32_t cardSize = SD.cardSize() / (1024 * 1024); String str = "SDCard Size: " + String(cardSize) + "MB"; Serial.println(str); } To learn more about using the microSD card with the ESP32, you can read the following guide: ESP32: Guide for MicroSD Card Module using Arduino IDE.

Start Serial Communication

Start a serial communication with the modem: SerialAT.begin(UART_BAUD, SERIAL_8N1, PIN_RX, PIN_TX);

Restart and Initialize the Modem

Call the following lines to restart the modem: // Restart takes quite some time // To skip it, call init() instead of restart() Serial.println("Initializing modem..."); if (!modem.restart()) { Serial.println("Failed to restart modem, attempting to continue without restarting"); } Or the following lines to initialize: // To skip it, call init() instead of restart() Serial.println("Initializing modem..."); if (!modem.init()) { Serial.println("Failed to restart modem, attempting to continue without restarting"); } Difference between restart() and init() according to documentation: restart() generally takes longer than init() but ensures the module doesn't have lingering connections.

Get Modem Name and Info

You can use the getModemName() and getModemInfo() to get information about the modem. String name = modem.getModemName(); delay(500); Serial.println("Modem Name: " + name); String modemInfo = modem.getModemInfo(); delay(500); Serial.println("Modem Info: " + modemInfo);

Connect GPRS

To connect GPRS using the APN details: if (!modem.gprsConnect(apn, gprsUser, gprsPass)) { delay(10000); return; } To check if it is connected, you can use the isGprsConnected() method: if (modem.isGprsConnected()) { Serial.println("connected"); } else { Serial.println("not connected"); }

Start GPS and Get Location

As mentioned previously, there are two versions of the LILYGO SIM7000G ESP32 board. The latest comes with active GPS antenna power controlwhen the module GPIO 4 is not turned on the antenna consumes only the static current of the LDO. This means we need to turn GPIO 4 on before getting GPS data to power the antenna. That's what the next lines do: // Set SIM7000G GPIO4 HIGH ,turn on GPS power // CMD:AT+SGPIO=0,4,1,1 // Only in version 20200415 is there a function to control GPS power modem.sendAT("+SGPIO=0,4,1,1"); if (modem.waitResponse(10000L) != 1) { DBG(" SGPIO=0,4,1,1 false "); } If you have the oldest version, you don't need those lines of code. You can start GPS using the enableGPS() method. modem.enableGPS(); Get latitude and longitude using the getGPS() method. float lat, lon; while (1) { if (modem.getGPS(&lat, &lon)) { Serial.printf("lat:%f lon:%f\n", lat, lon); break; } else { Serial.print("getGPS "); Serial.println(millis()); } delay(2000); } When you're done using GPS, you can turn it off using the disableGPS() method: modem.disableGPS(); And finally, turn off the power to the antenna by turning GPIO 4 off: // Set SIM7000G GPIO4 LOW ,turn off GPS power // CMD:AT+SGPIO=0,4,1,0 // Only in version 20200415 is there a function to control GPS power modem.sendAT("+SGPIO=0,4,1,0"); if (modem.waitResponse(10000L) != 1) { DBG(" SGPIO=0,4,1,0 false "); }

Sending SMS

To send an SMS, you can simply use the sendSMS() method and pass as arguments the recipient number and the message. res = modem.sendSMS(SMS_TARGET, String("Hello from ") + imei); DBG("SMS:", res ? "OK" : "fail");

Powering Down the Module

The LILYGO is supposed to work on a 18650 battery and solar panel, so we must cut power whenever it's not needed. So, it's useful to have a function to turn off the modem completely. You can use the poweroff() method or send the +CPOWD=1 AT command. modem.sendAT("+CPOWD=1"); if (modem.waitResponse(10000L) != 1) { DBG("+CPOWD=1"); } // The following command does the same as the previous lines modem.poweroff(); Serial.println("Poweroff."); And that's it for the most relevant parts of code.

Wrapping Up

In this tutorial, you learned how to use the LILYGO T-SIM7000G ESP32 board. This tutorial can also be applied if you're using a regular ESP32 connected to an external SIM7000G module. This module supports LTE, GPRS, and GPS, which can be very useful for IoT and Home Automation projects. You learned how to connect GPRS, how to send SMS messages and how to get GPS data. The idea is to include the snippets of code you need in your own projects. The LILYGO T-SIM7000G ESP32 board also comes with a microSD card slot that can be useful for datalogging projects or to save configuration settings. Furthermore, it comes with a battery holder and a battery charging circuit to use with solar panels. So, it's suitable to use in remote locations. However, I haven't experimented with the battery circuit yet. For more examples, you can explore the TinyGSM library repository or the official LILYGO T-SIM7000G Github page. Do you have one of these boards? What do you think? Let us know in the comments below. You may also like the following tutorials (that with minor changes can be used with the SIM7000G board): Connect ESP32 to Cloud MQTT Broker (TTGO T-Call ESP32 SIM800L) ESP32 SIM800L: Send Text Messages (SMS Alert) with Sensor Readings ESP32 Publish Data to Cloud without Wi-Fi (TTGO T-Call ESP32 SIM800L)

Getting Started with Deta Base (Unlimited and Free Database for Developers)

Get started with Deta Base using the ESP32 board. Deta Base is a NoSQL database. It is unlimited, free, and easy to use. Additionally, it requires minimal setup. So, it's perfect for your hobbyist projects and prototyping. You'll learn how to perform create, read, update, delete and query operations in a Deta Base instance using an ESP32. To interact with Deta base using the ESP32, we'll use the detaBaseArduinoESP32 library. The present tutorial was based on the guides created by the library developer. You can find the guides on the following link: ESP32 Deta Base Guides by Kushagra Goel

Introducing Deta Base

The best way to describe Deta Base:
Deta Base is a fully-managed, fast, scalable and secure NoSQL database with a focus on end-user simplicity. It offers a UI through which you can easily see, query, update and delete records in the database. https://docs.deta.sh/docs/base/about
And the best part is that Deta Base is free to use! If you're wondering where your data is saved and if it is secured, here's the answer:
Your data is encrypted and stored safely on AWS. Encryption keys are managed by AWS; AWS manages Exabytes of the world's most sensitive data. https://docs.deta.sh/docs/base/about#is-my-data-secure
We recommend taking a look at the docs to get more familiar with Deta Base: Deta Base documentation Deta Base is still in the beta version, so you may expect improvements in the future.

Creating a Deta Base Account

To get started, you need to create a Deta Base account. Go to deta.sh, and click on Join Deta to create a new account. Enter a username, password, and email to create a new account. Deta base will send you a verification email. Verify your account, and you should be redirected to your Deta dashboard. The following window pops up: By default, it creates a new project called default. As mentioned, projects are accessed via ids and keys. When you click on the See My Key button, you'll get your project id and key. Make sure you save it somewhere because you'll need those laterthe key will only be shown once. When you're done. Click Close. The library we'll use with the ESP32 automatically creates a Base (database table) instance for your project. So, you don't need to manually create it on the Deta Base interface.

Installing the Deta Base Library for ESP32

To interact with Deta Base using the ESP32, we'll use the detaBaseArduinoESP32 library. You can install the library in the Arduino IDE. Go to Sketch > Include Library > Manage Libraries. Search for detabasearduinoesp32 and install the library by Kushagra Goel.

Deta Base with ESP32: CRUD Operations

In this section, you'll learn how to program your ESP32 to perform CRUD (create, read, update, delete) operations and queries on Deta Base. The library provides a simple example showing how to do that. In the Arduino IDE, make sure you have an ESP32 board selected in Tools > Board. Then, go to File > Examples > detabaseAduinoESP32 and select the detaLibTest example. The following code should load. # Original Source: https://github.com/A223D/detaBaseArduinoESP32/blob/main/examples/detaLibTest/detaLibTest.ino #include <detaBaseArduinoESP32.h> #include <WiFiClientSecure.h> #define LED 2 char* apiKey = "MY_KEY"; char* detaID = "MY_ID"; char* detaBaseName = "MY_BASE"; WiFiClientSecure client; DetaBaseObject detaObj(client, detaID, detaBaseName, apiKey, true); void setup() { Serial.begin(115200); Serial.println("Let's begin initialization"); pinMode(LED, OUTPUT); digitalWrite(LED, LOW); Serial.println("Reached before WiFi init"); WiFi.begin("0xCAFE", "0xC0FFEE"); Serial.println("Waiting to connect to WiFi"); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(); digitalWrite(LED, HIGH); } // PUT "{\"items\":[{\"age\":4}]}" //INSERT "{\"item\":{\"key\":\"cba\",\"age\":4}}" //INSERT "{\"item\":{\"key\":\"abc\",\"age\":4}}" //UPDATE "{\"increment\":{\"age\":1}}", key:abc //UPDATE "{\"increment\":{\"age\":1}}", key:cba //QUERY "{\"query\":[{\"age?lt\": 10}]}" void loop() { printResult(detaObj.putObject("{\"items\":[{\"age\":4}]}")); Serial.println(); printResult(detaObj.getObject("cba")); Serial.println(); printResult(detaObj.deleteObject("abc")); Serial.println(); printResult(detaObj.insertObject("{\"item\":{\"key\":\"cba\",\"age\":4}}")); Serial.println(); printResult(detaObj.insertObject("{\"item\":{\"key\":\"abc\",\"age\":4}}")); Serial.println(); printResult(detaObj.updateObject("{\"increment\":{\"age\":1}}", "abc")); Serial.println(); printResult(detaObj.updateObject("{\"increment\":{\"age\":1}}", "bcs")); Serial.println(); printResult(detaObj.query("{\"query\":[{\"age?lt\": 10}]}")); Serial.println(); while (true); } View raw code You need to insert your project API KEY and ID. You also need to inserte a name for the databaseit can be whatever you want. I called it Test. char* apiKey = "REPLACE_WITH_YOUR_PROJECT_API_KEY"; char* detaID = "REPLACE_WITH_YOUR_PROJECT_ID"; char* detaBaseName = "Test"; In the setup(), you need to include your network credentials, SSID and password so that your ESP32 can connect to the internet. WiFi.begin(WIFI_SSID, WIFI_PASSWORD); You can upload the code now and it will work straight away. We recommend reading through the following section to understand how things actually work.

How it Works

Read this section to learn how to interact with Deta Base using the ESP32.

Include Libraries

First, include the detaBaseArduinoESP32 library. You also need to include the WiFiClientSecure librarythis automatically includes the WiFi.h library, also needed in this example. #include <detaBaseArduinoESP32.h> #include <WiFiClientSecure.h>

Deta Base Key, ID, and Name

Insert the project key, ID, and a name for the Base. As mentioned previously, if not created yet, the library will automatically create a Base instance for you on Deta Base. The name for the database can be whatever best describes it. In this case, I called it Test. If you've already created a Base instance manually on Deta Base, you can use it's name here. char* apiKey = "REPLACE_WITH_YOUR_PROJECT_API_KEY"; char* detaID = "REPLACE_WITH_YOUR_PROJECT_ID"; char* detaBaseName = "Test";

Creating a DetaBaseObject

Then, you need to create a WiFiClientSecure and a DetaBaseObject objects. The DetaBaseObject accepts as arguments the WiFi client, the project ID, base name, API key, lastly a boolean variable. This last boolean variable when set to true enables debugging statement. WiFiClientSecure client; //choose this: DetaBaseObject detaObj(client, detaID, detaBaseName, apiKey, true); //or this: //DetaBaseObject detaObj(client, detaID, detaBaseName, apiKey); The client is passed to the DetaBaseObject as is, without any modification. This is done because a root CA certificate is set in the DetaBaseObject constructor. This is required since we are making requests over HTTPS.

setup()

In the setup(), connect the ESP32 to Wi-Fi using your Wi-Fi credentials: SSID and password. void setup() { Serial.begin(115200); Serial.println("Let's begin initialization"); pinMode(LED, OUTPUT); digitalWrite(LED, LOW); Serial.println("Reached before WiFi init"); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); Serial.println("Waiting to connect to WiFi"); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(); digitalWrite(LED, HIGH); }

Insert

To insert items into the database, you can use the putObject() function. As described in the Deta Base documentation, a put operation expects a JSON object in the following format: { // array of items to put "items": [ { "key": {key}, // optional, a random key is generated if not provided "field1": "value1", // rest of item }, // rest of items ] } The key is optional, and will be assigned by Deta Base if not provided. If a key is provided, and an entry already exists with that key, it is overwritten. The following line of code: printResult(detaObj.putObject("{\"items\":[{\"age\":4}]}")); That contains the following JSON: { "items":[ { "age":"4" } ] } Adds the following to the database: { "age": 4 } A backslash character (\) is added before each to indicate an escape character, since we require in the JSON input. Note: Keys have to be strings. If you want to use a number as a key, make sure it is interpreted as a string by enclosing it in double-quotes. (Double quotes with back-slashes.) With PUT you can insert multiple items in a single request. For example: printResult(detaObj.putObject("{\"items\":[{\"age\":4,\"weight\":28}]}")); If the request succeeds, we will see a 200-level status code in the Serial monitor, as well as the entire object(s) with its key(s). If you go to your Deta Base project, you should see a new base instance and the item we've just inserted.

Retrieve an Object

You can retrieve an object by its key using the getObject() function. The function expects a key as argument. It can be an existing key or a non-existing key. The following line of code tries to retrieve an object with cba key. printResult(detaObj.getObject("cba")); You should get an error message because there isn't any object with that key on the database yet. However, if you manually create an object with the cba key and run the code again, it will retrieve the object with success. To create an object manually, you can click on the +Add button on the deta base interface. It will automatically create a new item with a predefined key. You can change it to cba.

Delete

To delete an object on the database, use the deleteObject() function. This function accepts as an argument the key of the object we want to delete. The output of this function returns a 200-level code that indicates success even though there's no object with that key. In our case, it tries to delete an object with the abc key. printResult(detaObj.deleteObject("abc")); However, if there was an object with the abc key it would be deleted.

Insert

To insert a new item in the database, you can use the insertObject() function that will make a POST request. This will create a new item only if no item with the same key exists. It expects a JSON in the following format: { "item": { "key": {key}, // optional // rest of item } } If you don't provide a key, Deta Base will automatically do that for you. In the example, the following line: printResult(detaObj.insertObject("{\"item\":{\"key\":\"cba\",\"age\":4}}")); Adds the following object to the database: { "key": "cba", "age": 4 } If you're already have an object with that key you'll get a 400-level code and an error in the payload message.

Update

The updateObject() method updates existing entries. It accepts as arguments the key for an existing objkect and a JSON object in the following format: { "set" : { //set some attribute to some value like //age: 10 }, "increment" :{ //increment some attribute by some value like //age: 1 }, "append": { //append some value to some list attribute like //names: ["John"] }, "prepend": { //append some value to some list attribute like //names: ["Sam"] }, "delete": [//attributes to be deleted] } All of those JSON sub-objects (set, increment, append, prepend, and delete) are optional. You can learn more about this type of request on the documentation. In our example, the following line will increment the age by 1 in the entry with the abc key. printResult(detaObj.updateObject("{\"increment\":{\"age\":1}}", "abc"));

Query Data

Deta Base also supports queries for fetching data that match certain conditions. You can learn more about Deta Base Queries on the following link: Deta Base Queries Documentation To make a query you can use the query() function that accepts as argument the query itself in JSON format (see the link to the documentation above). The following query will return all the objects whose value of age is less than 10, which in our case corresponds to all objects in the database. printResult(detaObj.query("{\"query\":[{\"age?lt\": 10}]}"));

Demonstration

After running the example, you should get the following on the Serial Monitor. And your database should look as follows.

Wrapping Up

In this tutorial, you learned how to interact with Deta Base using the ESP32. Deta Base is a NoSQL database, it's free and unlimited. This means you won't have to pay anything to use it, and you can add as many entries as needed. The database requires minimal setup and you can use it right away. Thanks to the detaBaseArduinoESP32 library, it's even easier to make HTTP requests to the database and handle the responses. In this example, you learned how to insert sample values. You can easily modify the examples to save sensor readings, for example, or GPIO states. The present tutorial was based on the tutorial created by Kushagra Goel. We hope you found this article useful. We have guides for other popular databases with the ESP32: ESP32: Getting Started with Firebase (Realtime Database) ESP32: Getting Started with InfluxDB (Time Series Database) ESP32/ESP8266 Insert Data into MySQL Database using PHP and Arduino IDE

SPI Communication: Set Pins, Multiple SPI Bus Interfaces, and Peripherals (Arduino IDE)

This is a simple guide about SPI communication protocol with the ESP32 using Arduino IDE. We'll take a look at the ESP32 SPI pins, how to connect SPI devices, define custom SPI pins, how to use multiple SPI devices, and much more. Table of Contents: Introducing ESP32 SPI Communication Protocol ESP32 SPI Peripherals ESP32 SPI Pins Using Custom ESP32 SPI Pins ESP32 with Multiple SPI Devices Multiple SPI Devices (same bus, different CS pin) Using Two SPI Bus Interfaces (use HSPI and VSPI simultaneously) This tutorial focus on programming the ESP32 using the Arduino core, so before proceeding, you should have the ESP32 add-on installed in your Arduino IDE. Follow the next tutorial to install the ESP32 on the Arduino IDE, if you haven't already. Installing the ESP32 Board in Arduino IDE (Windows, Mac OS X, and Linux instructions) Alternatively, you can also use VS Code with the PlatformIO extension to program your boards using the Arduino core: Getting Started with VS Code and PlatformIO IDE for ESP32 and ESP8266 (Windows, Mac OS X, Linux Ubuntu)

Introducing ESP32 SPI Communication Protocol

SPI stands for Serial Peripheral Interface, and it is a synchronous serial data protocol used by microcontrollers to communicate with one or more peripherals. For example, your ESP32 board communicating with a sensor that supports SPI or with another microcontroller. In an SPI communication, there is always a controller (also called master) that controls the peripheral devices (also called slaves). Data can be sent and received simultaneously. This means that the master can send data to a slave, and a slave can send data to the master at the same time. You can have only one master, which will be a microcontroller (the ESP32), but you can have multiple slaves. A slave can be a sensor, a display, a microSD card, etc., or another microcontroller. This means you can have an ESP32 connected to multiple sensors, but the same sensor can't be connected to multiple ESP32 boards simultaneously.

SPI Interface

For SPI communication you need four lines: MISO: Master In Slave Out MOSI: Master Out Slave In SCK: Serial Clock CS /SS: Chip Select (used to select the device when multiple peripherals are used on the same SPI bus) On a slave-only device, like sensors, displays, and others, you may find a different terminology: MISO may be labeled as SDO (Serial Data Out) MOSI may be labeled as SDI (Serial Data In)

ESP32 SPI Peripherals

The ESP32 integrates 4 SPI peripherals: SPI0, SPI1, SPI2 (commonly referred to as HSPI), and SPI3 (commonly referred to as VSPI). SP0 and SP1 are used internally to communicate with the built-in flash memory, and you should not use them for other tasks. You can use HSPI and VSPI to communicate with other devices. HSPI and VSPI have independent bus signals, and each bus can drive up to three SPI slaves.

ESP32 Default SPI Pins

Many ESP32 boards come with default SPI pins pre-assigned. The pin mapping for most boards is as follows:
SPI MOSI MISO SCLK CS
VSPI GPIO 23 GPIO 19 GPIO 18 GPIO 5
HSPI GPIO 13 GPIO 12 GPIO 14 GPIO 15
Warning: depending on the board you're using, the default SPI pins might be different. So, make sure you check the pinout for the board you're using. Additionally, some boards don't have pre-assigned SPI pins, so you need to set them on code. Note: usually, when not specified, the board will use the VSPI pins when initializing an SPI communication with the default settings. Whether your board comes with pre-assigned pins or not, you can always set them on code.

Finding your ESP32 Board's Default SPI Pins

If you're not sure about your board's default SPI pins, you can upload the following code to find out. /* Rui Santos Complete project details at https://RandomNerdTutorials.com/esp32-spi-communication-arduino/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ //Find the default SPI pins for your board //Make sure you have the right board selected in Tools > Boards void setup() { // put your setup code here, to run once: Serial.begin(115200); Serial.print("MOSI: "); Serial.println(MOSI); Serial.print("MISO: "); Serial.println(MISO); Serial.print("SCK: "); Serial.println(SCK); Serial.print("SS: "); Serial.println(SS); } void loop() { // put your main code here, to run repeatedly: } View raw code Important: make sure you select the board you're using in Tools > Board, otherwise, you may not get the right pins. After uploading the code, open the Serial Monitor, RST your board and you'll see the SPI pins.

Using Custom ESP32 SPI Pins

When using libraries to interface with your SPI peripherals, it's usually simple to use custom SPI pins because you can pass them as arguments to the library constructor. For example, take a quick look at the following example that interfaces with a BME280 sensor using the Adafruit_BME280 library. /* Rui Santos Complete project details at https://RandomNerdTutorials.com/esp32-spi-communication-arduino/ Based on the Adafruit_BME280_Library example: https://github.com/adafruit/Adafruit_BME280_Library/blob/master/examples/bme280test/bme280test.ino Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ #include <Wire.h> #include <Adafruit_Sensor.h> #include <Adafruit_BME280.h> #include <SPI.h> #define BME_SCK 25 #define BME_MISO 32 #define BME_MOSI 26 #define BME_CS 33 #define SEALEVELPRESSURE_HPA (1013.25) //Adafruit_BME280 bme; // I2C //Adafruit_BME280 bme(BME_CS); // hardware SPI Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // software SPI unsigned long delayTime; void setup() { Serial.begin(9600); Serial.println(F("BME280 test")); bool status; // default settings // (you can also pass in a Wire library object like &Wire2) status = bme.begin(); if (!status) { Serial.println("Could not find a valid BME280 sensor, check wiring!"); while (1); } Serial.println("-- Default Test --"); delayTime = 1000; Serial.println(); } void loop() { printValues(); delay(delayTime); } void printValues() { Serial.print("Temperature = "); Serial.print(bme.readTemperature()); Serial.println(" *C"); // Convert temperature to Fahrenheit /*Serial.print("Temperature = "); Serial.print(1.8 * bme.readTemperature() + 32); Serial.println(" *F");*/ Serial.print("Pressure = "); Serial.print(bme.readPressure() / 100.0F); Serial.println(" hPa"); Serial.print("Approx. Altitude = "); Serial.print(bme.readAltitude(SEALEVELPRESSURE_HPA)); Serial.println(" m"); Serial.print("Humidity = "); Serial.print(bme.readHumidity()); Serial.println(" %"); Serial.println(); } View raw code You can easily pass your custom SPI pins to the library constructor. Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); In that case, I was using the following SPI pins (not default) and everything worked as expected: #define BME_SCK 25 #define BME_MISO 32 #define BME_MOSI 26 #define BME_CS 33 If you're not using a library, or the library you're using doesn't accept the pins in the library constructor, you may need to initialize the SPI bus yourself. In that case, you would need to call the SPI.begin() method on the setup() and pass the SPI pins as arguments: SPI.begin(SCK, MISO, MOSI, SS); You can see an example of this scenario in this tutorial, in which we initialize an SPI LoRa transceiver that is connected to custom SPI pins. Or this example showing how to use custom SPI pins with a microSD card module.

ESP32 with Multiple SPI Devices

As we've seen previously, you can use two different SPI buses on the ESP32 and each bus can connect up to three different peripherals. This means that we can connect up to six SPI devices to the ESP32. If you need to use more, you can use an SPI multiplexer.

Multiple SPI Devices (same bus, different CS pin)

To connect multiple SPI devices, you can use the same SPI bus as long as each peripheral uses a different CS pin. To select the peripheral you want to communicate with, you should set its CS pin to LOW. For example, imagine you have peripheral 1 and peripheral 2. To read from peripheral 1, make sure its CS pin is set to LOW (here represented as CS_1): digitalWrite(CS_1, LOW); // enable CS pin to read from peripheral 1 /* use any SPI functions to communicate with peripheral 1 */ Then, at same point, you'll want to read from peripheral 2. You should disable peripheral 1 CS pin by setting it to HIGH, and enable peripheral 2 CS pin by setting it to LOW: digitalWrite(CS_1, HIGH); // disable CS pin from peripheral 1 digitalWrite(CS_2, LOW); // enable CS pin to read from peripheral 2 /* use any SPI functions to communicate with peripheral 2 */

ESP32 Using Two SPI Bus Interfaces (Use HSPI and VSPI simultaneously)

To communicate with multiple SPI peripherals simultaneously, you can use the ESP32 two SPI buses (HSPI and VSPI). You can use the default HSPI and VSPI pins or use custom pins. Briefly, to use HSPI and VSPI simultaneously, you just need to. 1) First, make sure you include the SPI library in your code. #include <SPI.h> 2) Initialize two SPIClass objects with different names, one on the HSPI bus and another on the VSPI bus. For example: vspi = new SPIClass(VSPI); hspi = new SPIClass(HSPI); 3) Call the begin() method on those objects. vspi.begin(); hspi.begin(); You can pass custom pins to the begin() method if needed. vspi.begin(VSPI_CLK, VSPI_MISO, VSPI_MOSI, VSPI_SS); hspi.begin(HSPI_CLK, HSPI_MISO, HSPI_MOSI, HSPI_SS); 4) Finally, you also need to set the SS pins as outputs. For example: pinMode(VSPI_SS, OUTPUT); pinMode(HSPI_SS, OUTPUT); Then, use the usual commands to interact with the SPI devices, whether you're using a sensor library or the SPI library methods. You can find an example of how to use multiple SPI buses on the arduino-esp32 SPI library. See the example below: /* The ESP32 has four SPi buses, however as of right now only two of * them are available to use, HSPI and VSPI. Simply using the SPI API * as illustrated in Arduino examples will use VSPI, leaving HSPI unused. * * However if we simply intialise two instance of the SPI class for both * of these buses both can be used. However when just using these the Arduino * way only will actually be outputting at a time. * * Logic analyser capture is in the same folder as this example as * "multiple_bus_output.png" * * created 30/04/2018 by Alistair Symonds */ #include <SPI.h> // Define ALTERNATE_PINS to use non-standard GPIO pins for SPI bus #ifdef ALTERNATE_PINS #define VSPI_MISO 2 #define VSPI_MOSI 4 #define VSPI_SCLK 0 #define VSPI_SS 33 #define HSPI_MISO 26 #define HSPI_MOSI 27 #define HSPI_SCLK 25 #define HSPI_SS 32 #else #define VSPI_MISO MISO #define VSPI_MOSI MOSI #define VSPI_SCLK SCK #define VSPI_SS SS #define HSPI_MISO 12 #define HSPI_MOSI 13 #define HSPI_SCLK 14 #define HSPI_SS 15 #endif #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 #define VSPI FSPI #endif static const int spiClk = 1000000; // 1 MHz //uninitalised pointers to SPI objects SPIClass * vspi = NULL; SPIClass * hspi = NULL; void setup() { //initialise two instances of the SPIClass attached to VSPI and HSPI respectively vspi = new SPIClass(VSPI); hspi = new SPIClass(HSPI); //clock miso mosi ss #ifndef ALTERNATE_PINS //initialise vspi with default pins //SCLK = 18, MISO = 19, MOSI = 23, SS = 5 vspi->begin(); #else //alternatively route through GPIO pins of your choice vspi->begin(VSPI_SCLK, VSPI_MISO, VSPI_MOSI, VSPI_SS); //SCLK, MISO, MOSI, SS #endif #ifndef ALTERNATE_PINS //initialise hspi with default pins //SCLK = 14, MISO = 12, MOSI = 13, SS = 15 hspi->begin(); #else //alternatively route through GPIO pins hspi->begin(HSPI_SCLK, HSPI_MISO, HSPI_MOSI, HSPI_SS); //SCLK, MISO, MOSI, SS #endif //set up slave select pins as outputs as the Arduino API //doesn't handle automatically pulling SS low pinMode(vspi->pinSS(), OUTPUT); //VSPI SS pinMode(hspi->pinSS(), OUTPUT); //HSPI SS } // the loop function runs over and over again until power down or reset void loop() { //use the SPI buses spiCommand(vspi, 0b01010101); // junk data to illustrate usage spiCommand(hspi, 0b11001100); delay(100); } void spiCommand(SPIClass *spi, byte data) { //use it as you would the regular arduino SPI API spi->beginTransaction(SPISettings(spiClk, MSBFIRST, SPI_MODE0)); digitalWrite(spi->pinSS(), LOW); //pull SS slow to prep other end for transfer spi->transfer(data); digitalWrite(spi->pinSS(), HIGH); //pull ss high to signify end of data transfer spi->endTransaction(); } View raw code

Wrapping Up

This article was a quick and simple guide showing you how to use SPI communication with the ESP32 using the Arduino corewith the ESP32 acting as a controller (master). In summary, the ESP32 has four SPI buses, but only two can be used to control peripherals, the HSPI and VSPI. Most ESP32 have pre-assigned HSPI and VSPI GPIOs, but you can always change the pin assignment in the code. You can use the HSPI and VSPI buses simultaneously to drive multiple SPI peripherals, or you can use multiple peripherals on the same bus as long as their CS pin is connected to a different GPIO. We didn't dive deeply into examples, because each sensor, library, and case scenario is different. But, now you should have a better idea of how to interface one or multiple SPI devices with the ESP32. For more detailed information about the SPI Master driver on the ESP32, you can check the espressif official documentation. We didn't cover setting the ESP32 as an SPI slave, but you can check these examples. We hope you find this tutorial useful. We have a similar article, but about I2C communication protocol. Check it out on the following link: ESP32 I2C Communication: Set Pins, Multiple Bus Interfaces and Peripherals (Arduino IDE)

Control ESP32/ESP8266 GPIOs from Anywhere (Firebase Web App)

In this guide, you'll create a Firebase Web App to control the ESP32 or ESP8266 GPIOs from anywhere. The access to the web app is protected with authentication using email and password. The GPIO states are saved on the Firebase Realtime Database. The web app writes to the database to change the GPIO states and the ESP boards are listening for database changes to update the GPIO states accordingly. This article is Part 2 of a previous tutorial. You need to complete one of the following tutorials before proceeding: Control ESP32 GPIOs from Anywhere using Firebase Control ESP8266 NodeMCU GPIOs from Anywhere using Firebase

Project Overview

In this tutorial (Part 2), you'll create a web app to control the ESP32 or ESP8266 GPIOs from anywhere. In a previous tutorial, you learned how to set the ESP32 or ESP8266 to listen to database changes and update its GPIOs accordingly. You changed the GPIO states manually on the Realtime Database using the Firebase console. Now, you'll create your own web app, hosted on Firebase, to control your boards from anywhere. The following diagram shows a high-level overview of the project we'll buildprogramming the ESP32/ESP8266 and setting up the Firebase Project was done in Part 1: Part 1ESP32 Part 1ESP8266 Firebase hosts your web app over a global CDN using Firebase Hosting and provides an SSL certificate. You can access your web app from anywhere using the Firebase-generated domain name. When you first access the web app, you need to authenticate with an authorized email address and password. You already set up that user and the authentication method in Part 1. After authentication, you can access a web app page that shows several buttons to change the GPIO states on the database. The ESP32 or ESP8266 is listening to database changes. When you click on the buttons, the GPIO states change on the database, and the ESP updates the states accordingly. The web app also shows what's the current state of the GPIOs. As an example, we'll control three GPIOs (12, 13, and 14). As mentioned in the previous tutorial, you can add/remove more GPIOs and boards or control other GPIOs. Once you're logged in, you can logout any time. The next time you'll access the app you'll need to login again. The following screenshot shows what the web page looks like on a computer web browser.

Prerequisites

Before start creating the Firebase Web App, you need to check the following prerequisites.

Creating a Firebase Project

You should have followed the one the following tutorials first: Control ESP32 GPIOs from Anywhere using Firebase Control ESP8266 NodeMCU GPIOs from Anywhere using Firebase The ESP32/ESP8266 must be running the code provided in that tutorial. The realtime database and authentication must be set up also as shown in the tutorial.

Install Required Software

Before getting started you need to install the required software to create the Firebase Web App. Here's a list of the software you need to install (click on the links for instructions): Visual Studio Code Node.JS LTS version Install Firebase Tools

1) Add an App to Your Firebase Project

1) Go to your Firebase project Console and add an app to your project by clicking on the +Add app button. 2) Select the web app icon. 3) Give your app a name. Then, check the box next to Also set up Firebase Hosting for this App. Click Register app. 4) Then, copy the firebaseConfig object and save it because you'll need it later. After this, you can also access the firebaseConfig object if you go to your Project settings in your Firebase console. 5) Click Next on the proceeding steps, and finally on Continue to console.

2) Setting Up a Firebase Web App Project (VS Code)

Follow the next steps to create a Firebase Web App Project using VS Code.

1) Creating a Project Folder

1) Create a folder on your computer where you want to save your Firebase projectfor example, Firebase-Project on the Desktop. 2) Open VS Code. Go to File > Open Folder and select the folder you've just created. 3) Go to Terminal > New Terminal. A new Terminal window should open on your project path.

2) Firebase Login

4) On the previous Terminal window, type the following: firebase login 5) You'll be asked to collect CLI usage and error reporting information. Enter n and press Enter to deny. Note: If you are already logged in, it will show a message saying: Already logged in as [email protected]. 6) After this, it will pop up a new window on your browser to login into your firebase account. 7) Allow Firebase CLI to access your google account. 8) After this, Firebase CLI login should be successful. You can close the browser window.

3) Initializing Web App Firebase Project

9) After successfully login in, run the following command to start a Firebase project directory in the current folder. firebase init 10) You'll be asked if you want to initialize a Firebase project in the current directory. Enter Y and hit Enter. 11) Then, use up and down arrows and the Space key to select the options. Select the following options: Realtime Database: Configure security rules file for Realtime Database and (optionally) provision default instance. Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys The selected options will show up with a green asterisk. Then, hit Enter. 12) Select the option Use an existing projectit should be highlighted in bluethen, hit Enter. 13) After that, select the Firebase project for this directoryit should be the project created in Part 1. In my case, it is called esp-firebase-demo. Then hit Enter. 14) Press Enter on the following question to select the default database security rules file: What file should be used for Realtime Database Security Rules? 15) Then, select the hosting options as shown below: What do you want to use as your public directory? Hit Enter to select public. Configure as a single-page app (rewrite urls to /index.html)? No Set up automatic builds and deploys with GitHub? No 16) The Firebase project should now be initialized successfully. Notice that VS code created some essential files under your project folder. The index.html file contains some HTML text to build a web page. For now, leave the default HTML text. The idea is to replace that with your own HTML text to build a custom web page for your needs. We'll do that later in this tutorial. 17) To check if everything went as expected, run the following command on the VS Code Terminal window. firebase deploy You should get a Deploy complete! message and an URL to the Project Console and the Hosting URL. 18) Copy the hosting URL and paste it into a web browser window. You should see the following web page. You can access that web page from anywhere in the world. The web page you've seen previously is built with the HTML file placed in the public folder of your Firebase project. By changing the content of that file, you can create your own web app. That's what we're going to do in the next section.

3) Creating Firebase Web App

Now that you've created a Firebase project app successfully on VS Code, follow the next steps to customize the app to display the sensor readings on a login-protected web page.

index.html

Copy the following to your index.html file. This HTML file creates a simple web page with ON and OFF buttons to control GPIOs 12, 13, and 14. If you aren't authenticated, it shows a login form. When you authenticate with an authorized user email and corresponding password, it shows the user interface with the buttons. <!DOCTYPE html> <!-- Complete Project Details at: https://RandomNerdTutorials.com/ --> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>ESP IoT Firebase App</title> <!-- update the version number as needed --> <script src="https://www.gstatic.com/firebasejs/8.8.1/firebase-app.js"></script> <!-- include only the Firebase features as you need --> <script src="https://www.gstatic.com/firebasejs/8.8.1/firebase-auth.js"></script> <script src="https://www.gstatic.com/firebasejs/8.8.1/firebase-database.js"></script> <script> // REPLACE WITH YOUR web app's Firebase configuration const firebaseConfig = { apiKey: "", authDomain: "", databaseURL: "", projectId: "", storageBucket: "", messagingSenderId: "", appId: "" }; // Initialize firebase firebase.initializeApp(firebaseConfig); // Make auth and database references const auth = firebase.auth(); const db = firebase.database(); </script> <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous"> <link rel="icon" type="image/png" href="favicon.png"> <link rel="stylesheet" type="text/css" href="style.css"> </head> <body> <!--TOP BAR--> <div> <h1>ESP GPIO Control <i></i></h2> </div> <!--AUTHENTICATION BAR (USER DETAILS/LOGOUT BUTTON)--> <div style="display: none;"> <p><span>User logged in</span> <span>USEREMAIL</span> <a href="/">(logout)</a> </p> </div> <!--LOGIN FORM--> <form style="display: none;"> <div> <label for="input-email"><b>Email</b></label> <input type="text" placeholder="Enter Username" required> <label for="input-password"><b>Password</b></label> <input type="password" placeholder="Enter Password" required> <button type="submit">Login</button> <p style="color:red;"></p> </div> </form> <!--CONTENT (SENSOR READINGS)--> <div style="display: none;"> <div> <!--CARD FOR GPIO 12--> <div> <p><i></i> GPIO 12</p> <p> <button>ON</button> <button>OFF</button> </p> <p>State:<span></span></p> </div> <!--CARD FOR GPIO 13--> <div> <p><i></i> GPIO 13</p> <p> <button>ON</button> <button>OFF</button> </p> <p>State:<span></span></p> </div> <!--CARD FOR GPIO 14--> <div> <p><i></i> GPIO 14</p> <p> <button>ON</button> <button>OFF</button> </p> <p>State:<span></span></p> </div> </div> </div> <script src="scripts/auth.js"></script> <script src="scripts/index.js"></script> </body> </html> View raw code You need to modify the code with your own firebaseConfig objectthe one you've got in this step.

How it Works

Let's take a quick look at the HTML file, or skip to the next section. In the <head> of the HTML file, we must add all the required metadata. The title of the web page is ESP Firebase App, but you can change it in the following line. <title>ESP Firebase App</title> You must add the following line to be able to use Firebase with your app. <script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js"></script> You must also add any Firebase products you want to use. In this example, we're using the Realtime Database and Authentication. <script src="https://www.gstatic.com/firebasejs/8.8.1/firebase-auth.js"></script> <script src="https://www.gstatic.com/firebasejs/8.8.1/firebase-database.js"></script> Then, replace the firebaseConfig object with the one you've gotten from this step. const firebaseConfig = { apiKey: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", authDomain: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", databaseURL: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", projectId: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", storageBucket: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", messagingSenderId: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", appId: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION" }; Finally, Firebase is initialized, and we create two global variables db and auth that refer to Firebase authentication and to Firebase realtime database. // Initialize firebase firebase.initializeApp(firebaseConfig); // Make auth and database references const auth = firebase.auth(); const db = firebase.database(); The following line allows us to use fontawesome icons: <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous"> The next includes a favicon on our web page. <link rel="icon" type="image/png" href="favicon.png"> Finally, reference an external style.css file to format the HTML page. <link rel="stylesheet" type="text/css" href="style.css"> We're done with the metadata. Now, let's go to the HTML parts that are visible to the usergo between the <body> and </body> tags. We create a top navigation bar with the name of our app and a small icon from fontawesome. <!--TOP BAR--> <div> <h1>ESP GPIO Control <i></i></h2> </div> The following lines create a bar with the details of the authenticated user (email). It also shows a logout link to log out the user. <div style="display: none;"> <p><span>User logged in</span> <span>USEREMAIL</span> <a href="/">(logout)</a> </p> </div> First, we set the display style of all elements to none. We'll hide and show content depending if the user is authenticated or notwe'll handle that using JavaScript. Next, the following lines create the login form with an input field for the email and an input field for the password: <form style="display: none;"> <div> <label for="input-email"><b>Email</b></label> <input type="text" placeholder="Enter Username" required> <label for="input-password"><b>Password</b></label> <input type="password" placeholder="Enter Password" required> <button type="submit">Login</button> <p style="color:red;"></p> </div> </form> Inside the form, there's also a paragraph to display an error message if the login fails. <p style="color:red;"></p> Finally, we create a grid to display different cards for the GPIOs. <div style="display: none;"> <div> For example, the following lines create a card for GPIO 12: <!--CARD FOR GPIO 12--> <div> <p><i></i> GPIO 12</p> <p> <button>ON</button> <button>OFF</button> </p> <p>State:<span></span></p> </div> The buttons have specific ids so that we can refer to them later on in the Javascript files. There's also a span tag with a specific id to insert the GPIO state. Creating the cards for the other GPIOs is similar: <!--CARD FOR GPIO 13--> <div> <p><i></i> GPIO 13</p> <p> <button>ON</button> <button>OFF</button> </p> <p>State:<span></span></p> </div> <!--CARD FOR GPIO 14--> <div> <p><i></i> GPIO 14</p> <p> <button>ON</button> <button>OFF</button> </p> <p>State:<span></span></p> </div> It's important to keep in mind the ids of each of those elements, so that's its easier to identify them on the JavaScript file. You can use any other ids that make sense for your project.
GPIO 12 GPIO 13 GPIO 14
ON Button btn1On btn2On btn3On
OFF Button btn1Off btn2Off btn3Off
State state1 state2 state3
Finally, we need to add references to the external JavaScript files. For our application, we'll create two JavaScript files: auth.js (that handles everything related to the authentication) and index.js that handles everything related to the UI. We'll create those files inside a folder called scripts inside the public folder of our application. <script src="scripts/auth.js"></script> <script src="scripts/index.js"></script> After making the necessary changes (inserting your firebaseConfig object), you can save the HTML file.

style.css

Inside the public folder create a file called style.css. To create the file, select the public folder, and then click on the +file icon at the top of the File Explorer. Call it style.css. Then, copy the following to the style.css file html { font-family: Verdana, Geneva, Tahoma, sans-serif; display: inline-block; text-align: center; } h1 { font-size: 1.8rem; color: white; } .topnav { overflow: hidden; background-color: #049faa; color: white; font-size: 1rem; padding: 10px; } #authentication-bar{ background-color:mintcream; padding-top: 10px; padding-bottom: 10px; } #user-details{ color: cadetblue; } .content { padding: 20px; } body { margin: 0; } .card-grid { max-width: 800px; margin: 0 auto; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); } .card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); } .card-title { font-size: 1.2rem; color: #034078 } .state { color:#1282A2; } button { background-color: #049faa; color: white; padding: 14px 20px; margin: 8px 0; border: none; cursor: pointer; border-radius: 4px; } .button-on { background-color:#034078; } .button-on:hover { background-color: #1282A2; } .button-off { background-color:#858585; } .button-off:hover { background-color: #252524; } .form-elements-container{ padding: 16px; width: 250px; margin: 0 auto; } input[type=text], input[type=password] { width: 100%; padding: 12px 20px; margin: 8px 0; display: inline-block; border: 1px solid #ccc; box-sizing: border-box; } View raw code The CSS file includes some simple styles to make our webpage look better. We won't discuss how CSS works in this tutorial. You can easily modify the CSS file to change the colors and font size, for example.

JavaScript Files

We'll create two JavaScript files (auth.js and index.js) inside a scripts folder inside the public folder. Select the public folder, then click on the +folder icon to create a new folder. Call scripts to that new folder. Then, select the scripts folder and click on the +file icon. Create a file called auth.js. Then, repeat the previous steps to create an index.js. The following image shows how your web app project folder structure should look like.

auth.js

Now let's implement user sign-in using Firebase authentication. We'll implement sign-in using email and password. Copy the following to the auth.js file you created previously. document.addEventListener("DOMContentLoaded", function(){ // listen for auth status changes auth.onAuthStateChanged(user => { if (user) { console.log("user logged in"); console.log(user); setupUI(user); } else { console.log("user logged out"); setupUI(); } }); // login const loginForm = document.querySelector('#login-form'); loginForm.addEventListener('submit', (e) => { e.preventDefault(); // get user info const email = loginForm['input-email'].value; const password = loginForm['input-password'].value; // log the user in auth.signInWithEmailAndPassword(email, password).then((cred) => { // close the login modal & reset form loginForm.reset(); console.log(email); }) .catch((error) =>{ const errorCode = error.code; const errorMessage = error.message; document.getElementById("error-message").innerHTML = errorMessage; console.log(errorMessage); }); }); // logout const logout = document.querySelector('#logout-link'); logout.addEventListener('click', (e) => { e.preventDefault(); auth.signOut(); }); }); View raw code Then, save the file. This file takes care of everything related to the login and logout of the user. Continue reading to learn how the code works or skip to the next section. Login The following lines are responsible for logging in the user. const loginForm = document.querySelector('#login-form'); loginForm.addEventListener('submit', (e) => { e.preventDefault(); // get user info const email = loginForm['input-email'].value; const password = loginForm['input-password'].value; // log the user in auth.signInWithEmailAndPassword(email, password).then((cred) => { // close the login modal & reset form loginForm.reset(); console.log(email); }) .catch((error) =>{ const errorCode = error.code; const errorMessage = error.message; document.getElementById("error-message").innerHTML = errorMessage; console.log(errorMessage); }); }); We create a variable that refers to the login form HTML element called loginForm. const loginForm = document.querySelector('#login-form'); If you go back to the index.html file, you can see that the form has the login-form id. We add an event listener of type submit to the form. This means that the subsequent instructions will run whenever the form is submitted. loginForm.addEventListener('submit', (e) => { You can get the submitted data as follows. const email = loginForm['input-email'].value; const password = loginForm['input-password'].value; If you go back to the HTML file, you'll see that the input fields contain the following ids: input-email and input-password for the email and password, respectively. Now that we have the inserted email and password, we can try to log in to Firebase. To do that, pass the user's email address and password to the following method: signInWithEmailAndPassword: auth.signInWithEmailAndPassword(email, password).then((cred) => { After logging in, we reset the form and print the user email in the console. auth.signInWithEmailAndPassword(email, password).then((cred) => { // close the login modal & reset form loginForm.reset(); console.log(email); }) In case there is an error signing in, we catch the error message, and display it on the error-message HTML element (a paragraph below the form). .catch((error) =>{ const errorCode = error.code; const errorMessage = error.message; document.getElementById("error-message").innerHTML = errorMessage; console.log(errorMessage); }); Logout The following snippet is responsible for logging out the user. const logout = document.querySelector('#logout-link'); logout.addEventListener('click', (e) => { e.preventDefault(); auth.signOut(); }); When the user is logged in, a logout link is visible in the authentication bar. That link has the logout-link id (see on the HTML file). So, first, we create a variable called logout that refers to the logout link. const logout = document.querySelector('#logout-link'); Then, we add an event listener of type click. This means the subsequent instructions will run whenever you click on the logout link. logout.addEventListener('click', (e) => { When the button is clicked, we sign out the user using the signOut method. auth.signOut(); Auth State Changes To keep track of the user authentication stateto know if the user is logged in or logged out, there is a method called onAuthSateChanged that allows you to receive an event whenever the authentication state changes. auth.onAuthStateChanged(user => { if (user) { console.log("user logged in"); console.log(user); setupUI(user); var uid = user.uid; console.log(uid); } else { console.log("user logged out"); setupUI(); } }); If the user returned is null, the user is currently signed out. Otherwise, it is currently signed in. In both scenarios, we print the current user state to the console and call the setupUI() function. We haven't created that function yet (we'll create it in the next section), but it will be responsible for handling the user interface accordingly to the authentication state. When the user is logged in, we pass the user as an argument to the setupUI() function. In this case, we'll display the complete user interface to show the buttons to control the GPIOs, as you'll see later. if (user) { console.log("user logged in"); console.log(user); setupUI(user); If the user is logged out, we call the setupUI() function without any argument. In that scenario, we'll simply display a message informing that the user is logged out and doesn't have access to the interface (as we'll see later). } else { console.log("user logged out"); setupUI(); }

index.js

The index.js file handles the UI it shows the right content depending on the user authentication status. When the user is logged in, it gets the GPIO states from the database and updates the GPIO states on the interface. Then, it changes the states whenever you press the buttons. Copy the following to the index.js file. const loginElement = document.querySelector('#login-form'); const contentElement = document.querySelector("#content-sign-in"); const userDetailsElement = document.querySelector('#user-details'); const authBarElement = document.querySelector("#authentication-bar"); // Elements for GPIO states const stateElement1 = document.getElementById("state1"); const stateElement2 = document.getElementById("state2"); const stateElement3 = document.getElementById("state3"); // Button Elements const btn1On = document.getElementById('btn1On'); const btn1Off = document.getElementById('btn1Off'); const btn2On = document.getElementById('btn2On'); const btn2Off = document.getElementById('btn2Off'); const btn3On = document.getElementById('btn3On'); const btn3Off = document.getElementById('btn3Off'); // Database path for GPIO states var dbPathOutput1 = 'board1/outputs/digital/12'; var dbPathOutput2 = 'board1/outputs/digital/13'; var dbPathOutput3 = 'board1/outputs/digital/14'; // Database references var dbRefOutput1 = firebase.database().ref().child(dbPathOutput1); var dbRefOutput2 = firebase.database().ref().child(dbPathOutput2); var dbRefOutput3 = firebase.database().ref().child(dbPathOutput3); // MANAGE LOGIN/LOGOUT UI const setupUI = (user) => { if (user) { //toggle UI elements loginElement.style.display = 'none'; contentElement.style.display = 'block'; authBarElement.style.display ='block'; userDetailsElement.style.display ='block'; userDetailsElement.innerHTML = user.email; //Update states depending on the database value dbRefOutput1.on('value', snap => { if(snap.val()==1) { stateElement1.innerText="ON"; } else{ stateElement1.innerText="OFF"; } }); dbRefOutput2.on('value', snap => { if(snap.val()==1) { stateElement2.innerText="ON"; } else{ stateElement2.innerText="OFF"; } }); dbRefOutput3.on('value', snap => { if(snap.val()==1) { stateElement3.innerText="ON"; } else{ stateElement3.innerText="OFF"; } }); // Update database uppon button click btn1On.onclick = () =>{ dbRefOutput1.set(1); } btn1Off.onclick = () =>{ dbRefOutput1.set(0); } btn2On.onclick = () =>{ dbRefOutput2.set(1); } btn2Off.onclick = () =>{ dbRefOutput2.set(0); } btn3On.onclick = () =>{ dbRefOutput3.set(1); } btn3Off.onclick = () =>{ dbRefOutput3.set(0); } // if user is logged out } else{ // toggle UI elements loginElement.style.display = 'block'; authBarElement.style.display ='none'; userDetailsElement.style.display ='none'; contentElement.style.display = 'none'; } } View raw code Continue reading to learn how the code works or skip to the next section. Getting HTML Elements First, we create variables to refer to several elements on the UI interface by referring to their ids. To identify these elements, we recommend that you take a look at the HTML file provided and find the elements with the referred ids. const loginElement = document.querySelector('#login-form'); const contentElement = document.querySelector("#content-sign-in"); const userDetailsElement = document.querySelector('#user-details'); const authBarElement = document.querySelector("#authentication-bar"); The loginElement corresponds to the login form. The contentElement corresponds to the section of the web page that is visible when the user is logged in (that shows the sensor readings). The userDetailsElement corresponds to a section that will display the email of the logged in user. The auhtBarElement corresponds to the authentication bar that shows the current user status, the email of the authenticated user and the logout link. The following creates variables to refer to the buttons as GPIOs states on the interface: // Elements for GPIO states const stateElement1 = document.getElementById("state1"); const stateElement2 = document.getElementById("state2"); const stateElement3 = document.getElementById("state3"); // Button Elements const btn1On = document.getElementById('btn1On'); const btn1Off = document.getElementById('btn1Off'); const btn2On = document.getElementById('btn2On'); const btn2Off = document.getElementById('btn2Off'); const btn3On = document.getElementById('btn3On'); const btn3Off = document.getElementById('btn3Off');

Database Paths and References

Then, we need to create database paths to where the GPIOs states are saved: // Database path for GPIO states var dbPathOutput1 = 'board1/outputs/digital/12'; var dbPathOutput2 = 'board1/outputs/digital/13'; var dbPathOutput3 = 'board1/outputs/digital/14'; To be able to interact with the database on those paths, we need to create database references using those paths: // Database references var dbRefOutput1 = firebase.database().ref().child(dbPathOutput1); var dbRefOutput2 = firebase.database().ref().child(dbPathOutput2); var dbRefOutput3 = firebase.database().ref().child(dbPathOutput3); sertupUI() Function Then, we create the setupUI() function that will handle the UI accordingly to the state of the user authentication. In the auth.js file, we called the setupUI() function with the user argument setupUI(user) if the user is logged in; or the function without argument setupUI() when the user is logged out. So, let's check what happens when the user is logged in. if (user) { We show the authentication bar (that shows the user details and the logout link). To do that, we can set its display style to block. We also want the web page's main content with the sensor readings to be visible. contentElement.style.display = 'block'; authBarElement.style.display ='block'; Finally, we can get the logged in user email with user.email and display it in the userDetailsElement section as follows: userDetailsElement.innerHTML = user.email; Get GPIO States The following lines get the GPIO states whenever there's a change in the database and update the corresponding HTML elements with the new values. //Update states depending on the database value dbRefOutput1.on('value', snap => { if(snap.val()==1) { stateElement1.innerText="ON"; } else{ stateElement1.innerText="OFF"; } }); dbRefOutput2.on('value', snap => { if(snap.val()==1) { stateElement2.innerText="ON"; } else{ stateElement2.innerText="OFF"; } }); dbRefOutput3.on('value', snap => { if(snap.val()==1) { stateElement3.innerText="ON"; } else{ stateElement3.innerText="OFF"; } });

Button Events

Then, we add events to the buttons to write 1 or 0 to the corresponding database path accordingly to the button that was pressed. // Update database upon button click btn1On.onclick = () =>{ dbRefOutput1.set(1); } btn1Off.onclick = () =>{ dbRefOutput1.set(0); } btn2On.onclick = () =>{ dbRefOutput2.set(1); } btn2Off.onclick = () =>{ dbRefOutput2.set(0); } btn3On.onclick = () =>{ dbRefOutput3.set(1); } btn3Off.onclick = () =>{ dbRefOutput3.set(0); } Logged Out UI The following snippet handles the UI when the user logs out. We want to hide the authentication bar and the main webpage content (GPIO states and corresponding buttons) and show the login form. } else{ // toggle UI elements loginElement.style.display = 'block'; authBarElement.style.display ='none'; contentElement.style.display = 'none'; }

Favicon File

To display a favicon in your web app, you need to move the picture you want to use as favicon to the public folder. The picture should be called favicon.png. You can simply drag the favicon file from your computer into the public folder in VS Code. We're using the following icon as a favicon for our web app: favicon.png

Deploy your App

After saving the HTML, CSS, and JavaScript files, deploy your app on VS Code by running the following command on the Terminal window. firebase deploy The Terminal should display something as follows: Firebase offers a free hosting service to serve your assets and web apps. Then, you can access your web app from anywhere. You can use the Hosting URL provided to access your web app from anywhere.

Demonstration

Congratulations! You successfully deployed your app. It is now hosted on a global CDN using Firebase hosting. You can access your web app from anywhere on the Hosting URL provided. In my case, it is https://esp-firebase-demo.web.app. The web app is responsive, and you can access it using your smartphone, computer, or tablet. Insert the email and password of the authorized user you added in the Firebase Authentication methods. After that, you can access the dashboard to control the ESP32 or ESP8266 GPIOs. Go to your project's Firebase console Hosting tab. You can see your app domains, deploy history, and you can even roll back to previous versions of your app.

Wrapping Up

In this tutorial, you learned how to create a Firebase Web App with login/logout authentication to control the ESP32 or ESP8266 GPIOs from anywhere. The GPIO states are saved on the realtime database and the ESP is listening to any changes that occur in the database to update the GPIOs right away. You can easily add more GPIOs or more boards to this project. The database is protected using database rules. Only authorized authenticated users can access the web app to control the GPIOs. You can combine this project with other ESP32/ESP8266 Firebase projects we have published and add more functionalities to your app.

Firebase: Control ESP32 GPIOs from Anywhere

In this guide, you'll learn how to control the ESP32 GPIOs from anywhere using Firebase. We'll create nodes on the Firebase Realtime Database to save the current GPIO states. Whenever there's a change in the database nodes, the ESP32 updates its GPIOs accordingly. You can change the GPIOs states by writing on the database yourself, or you can create a web app to do that (check this tutorial). PART 2: Control ESP32/ESP8266 GPIOs from Anywhere (Firebase Web App)

What is Firebase?

Firebase is Google's mobile application development platform that helps you build, improve, and grow your app. Firebase provides free services like hosting, authentication, and realtime database that allow you to build a fully-featured web app to control and monitor the ESP32 and ESP8266 boards that would be much more difficult and laborious to build and set up on your own.

Project Overview

The following diagram shows a high-level overview of the project we'll build.
    The ESP32 authenticates as a user with email and password to be able to access the database (that user must be added on the Firebase authentication methods); The database is protected using database rules. We'll add the following rule: only authenticated users can access the database; The database has several nodes that save the ESP32 GPIO states. As an example, we'll control three GPIOs (12, 13, and 14). You can add or remove nodes to control more or less GPIOs. The ESP32 will listen for changes on the GPIOs database nodes. Whenever there's a change, it will update the GPIO states accordingly. You can change the GPIO states manually on the database using the Firebase console, or you can create a web page (accessible from anywhere) with buttons to control the GPIOs and show the current GPIO states (check PART 2).
These are the main steps to complete this project:
    Create a Firebase Project Set Authentication Methods Get Project API Key Set up the Realtime Database Set up Database Security Rules Organizing your Database Nodes ESP32: Listening for Database Changes (control GPIOs)

Preparing Arduino IDE

For this tutorial, we'll program the ESP32 board using the Arduino core. So, make sure you have the ESP32 add-on installed in your Arduino IDE: Installing the ESP32 Board in Arduino IDE (Windows, Mac OS X, Linux) If you want to program the ESP boards using VS Code with the PlatformIO extension, follow the next tutorial instead: Getting Started with VS Code and PlatformIO IDE for ESP32 and ESP8266

1) Create Firebase Proje3t

1) Go to Firebase and sign in using a Google Account. 2) Click Get Started and then Add project to create a new project. 3) Give a name to your project, for example ESP Firebase Demo. 4) Disable the option Enable Google Analytics for this project as it is not needed and click Create project. 5) It will take a few seconds to set up your project. Then, click Continue when it's ready. 6) You'll be redirected to your Project console page.

2) Set Authentication Methods

To allow authentication with email and password, first, you need to set authentication methods for your app. Most apps need to know the identity of a user. In other words, it takes care of logging in and identifying the users (in this case, the ESP32 or ESP8266). Knowing a user's identity allows an app to securely save user data in the cloud and provide the same personalized experience across all of the user's devices. To learn more about the authentication methods, you can read the documentation. 1) On the left sidebar, click on Authentication and then on Get started. 2) Select the Option Email/Password. 3) Enable that authentication method and click Save. 4) The authentication with email and password should now be enabled. 5) Now, you need to add a user. On the Authentication tab, select the Users tab at the top. Then, click on Add User. 6) Add an email address for the authorized user. It can be your google account email or any other email. You can also create an email for this specific project. Add a password that will allow you to sign in to your app and access the database. Don't forget to save the password in a safe place because you'll need it later. When you're done, click Add user. 7) A new user was successfully created and added to the Users table. Notice that Firebase creates a unique UID for each registered user. The user UID allows us to identify the user and keep track of the user to provide or deny access to the project or the database. There's also a column that registers the date of the last sign-in. At the moment, it is empty because we haven't signed in with that user yet. Copy the User UID because you'll need it later.

3) Get Project API Key

To interface with your Firebase project using the ESP32 or ESP8266 boards, you need to get your project API key. Follow the next steps to get your project API key. 1) On the left sidebar, click on Project Settings. 2) Copy the Web API Key to a safe place because you'll need it later.

4) Set up the Realtime Database

Now, let's create a realtime database and set up database rules for our project. 1) On the left sidebar, click on Realtime Database and then click on Create Database. 2) Select your database location. It should be the closest to your location. 3) Set up security rules for your database. You can select Start in test mode. We'll change the database rules in just a moment. 4) Your database is now created. You need to copy and save the database URLhighlighted in the following imagebecause you'll need it later in your ESP32 code.

5) Set up Database Security Rules

Now, let's set up the database rules. On the Realtime Database tab, select the Rules tab at the top. Then, click on Edit rules, and add the following rules. { "rules": { ".read": "auth.uid === 'REPLACE_WITH_YOUR_USER_UID'", ".write": "auth.uid === 'REPLACE_WITH_YOUR_USER_UID'" } } Insert the UID of the user you created previously. Then, click Publish. These database rules determine that: Only the user with that specific UID can read and write to the database (change the GPIO states).

Add More Users

To add more users, you can simply go to the Authentication tab and click Add user. Add an email and password for the new user, and finally click on Add user to create the user. Copy the user UID of that new user and add it to the database rules, as follows: { "rules": { ".read": "auth.uid === 'REPLACE_WITH_YOUR_USER_UID' || auth.uid === 'REPLACE_WITH_USER_UID2'", ".write": "auth.uid === 'REPLACE_WITH_YOUR_USER_UID' || auth.uid === 'REPLACE_WITH_USER_UID2'" } } For example. In my case, the UIDs of the users are: RjO3taAzMMXB82Xmir2LQ7XXXXXX and 9QdDc9as5mRXGAjEsQiUJkXXXXXX. So, the database rules will look as follows: { "rules": { ".read": "auth.uid === 'RjO3taAzMMXB82Xmir2LQ7XXXXXX' || auth.uid === '9QdDc9as5mRXGAjEsQiUJkXXXXXX'", ".write": "auth.uid === 'RjO3taAzMMXB82Xmir2LQ7XXXXXX' || auth.uid === '9QdDc9as5mRXGAjEsQiUJkXXXXXX'" } } Finally, Publish your database rules. To learn more about database rules, you can check the Firebase documentation.

6) Organizing Your Database Nodes

All the data stored in the Firebase Realtime Database is stored as JSON objects. So, you can think of the database as a cloud-based JSON tree. When you add data to the JSON tree, it becomes a node with an associated key in the existing JSON structure. Not familiar with JSON? Read this quick guide. The best way to organize your data will depend on your project features and how users access the data. We want to control the ESP32 GPIOs. We can organize the data in a way that makes it easy to add more GPIOs and boards later on. So, we can structure the database as follows: board1:outputs:digital: 12: 0 13: 0 14: 0 In JSON format, here's what it would look like: { "board1": { "outputs": { "digital": { "12": 0, "13": 0, "14": 0 } } } }

Creating Database Nodes

Now let's create the database nodes in our database. You can create the nodes manually by writing the nodes on the Firebase console, on the web app, or via the ESP32. We'll create them manually, so it is easier to follow the tutorial. 1) Click on the Realtime Database so that we start creating the nodes. 2) You can create the database nodes manually by using the (+) icons on the database. However, to prevent typos, we provide a JSON file that you can upload to create the same nodes as ours. Click the link below to download the JSON file. Download database JSON file After downloading, unzip the folder to access the .json file. 3) Now, go back to your database on the Firebase console. Click on the three-dot icon and select Import JSON. 4) Select the JSON file that you've just downloaded. 5) Your database should look as shown below. All the database nodes required for this project are created. You can proceed to the next section.

7) ESP32: Listening for Database Changes (control GPIOs)

In this section, we'll program the ESP32 board to do the following tasks:
    Authenticate as a user with email and password (the user you set up in this section); Listening for database changes on the GPIO nodes and changing their states accordingly.

Parts Required

For this project, you need the following parts: ESP32 (read Best ESP32 Development Boards); 3x LEDs; 3x 220Ohm resistors; Breadboard; Jumper wires. You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Schematic Diagram

In this example, we'll control three LEDs connected to GPIOs 12, 13, and 14. So, wire three LEDs to the ESP32. You can follow the next schematic diagram. You can use any other suitable ESP32 GPIOs, but you also need to change the database nodes.

Installing the Firebase ESP Client Library

The Firebase ESP Client Library provides many examples on how to interface the ESP32 with Firebase services. Check the library Github page here and consider supporting the author if the library is useful for your projects.

Install the Firebase-ESP-Client Library (Arduino IDE)

Follow this section if you're using Arduino IDE. Go to Sketch > Include Library > Manage Libraries, search for Firebase ESP Client. Select the Firebase Arduino Client Library for ESP8266 and ESP32. Now, you're all set to start programming the ESP32 boards to interact with the database.

Install the Firebase-ESP-Client Library (VS Code)

Follow the next instructions if you're using VS Code + PlatformIO. Click on the PIO Home icon and select the Libraries tab. Search for Firebase ESP Client. Select the Firebase Arduino Client Library for ESP8266 and ESP32. Then, click Add to Project and select the project you're working on. Also, change the monitor speed to 115200 by adding the following line to the platformio.ini file of your project: monitor_speed = 115200

Listening for Database Changes (GPIO states) Code

Copy the following code to your Arduino IDE or to the main.cpp file if you're using VS Code. You need to insert the following in the code before uploading it to your board: your network credentials project API key database URL authorized user email and password /* Rui Santos Complete project details at our blog. - ESP32: https://RandomNerdTutorials.com/firebase-control-esp32-gpios/ - ESP8266: https://RandomNerdTutorials.com/firebase-control-esp8266-nodemcu-gpios/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Based in the RTDB Basic Example by Firebase-ESP-Client library by mobizt https://github.com/mobizt/Firebase-ESP-Client/blob/main/examples/RTDB/Basic/Basic.ino */ #include <Arduino.h> #if defined(ESP32) #include <WiFi.h> #elif defined(ESP8266) #include <ESP8266WiFi.h> #endif #include <Firebase_ESP_Client.h> // Provide the token generation process info. #include "addons/TokenHelper.h" // Provide the RTDB payload printing info and other helper functions. #include "addons/RTDBHelper.h" // Insert your network credentials #define WIFI_SSID "REPLACE_WITH_YOUR_SSID" #define WIFI_PASSWORD "REPLACE_WITH_YOUR_PASSWORD" // Insert Firebase project API Key #define API_KEY "REPLACE_WITH_YOUR_PROJECT_API_KEY" // Insert Authorized Username and Corresponding Password #define USER_EMAIL "REPLACE_WITH_THE_USER_EMAIL" #define USER_PASSWORD "REPLACE_WITH_THE_USER_PASSWORD" // Insert RTDB URLefine the RTDB URL #define DATABASE_URL "REPLACE_WITH_YOUR_DATABASE_URL" // Define Firebase objects FirebaseData stream; FirebaseAuth auth; FirebaseConfig config; // Variables to save database paths String listenerPath = "board1/outputs/digital/"; // Declare outputs const int output1 = 12; const int output2 = 13; const int output3 = 14; // Initialize WiFi void initWiFi() { WiFi.begin(WIFI_SSID, WIFI_PASSWORD); Serial.print("Connecting to WiFi .."); while (WiFi.status() != WL_CONNECTED) { Serial.print('.'); delay(1000); } Serial.println(WiFi.localIP()); Serial.println(); } // Callback function that runs on database changes void streamCallback(FirebaseStream data){ Serial.printf("stream path, %s\nevent path, %s\ndata type, %s\nevent type, %s\n\n", data.streamPath().c_str(), data.dataPath().c_str(), data.dataType().c_str(), data.eventType().c_str()); printResult(data); //see addons/RTDBHelper.h Serial.println(); // Get the path that triggered the function String streamPath = String(data.dataPath()); // if the data returned is an integer, there was a change on the GPIO state on the following path /{gpio_number} if (data.dataTypeEnum() == fb_esp_rtdb_data_type_integer){ String gpio = streamPath.substring(1); int state = data.intData(); Serial.print("GPIO: "); Serial.println(gpio); Serial.print("STATE: "); Serial.println(state); digitalWrite(gpio.toInt(), state); } /* When it first runs, it is triggered on the root (/) path and returns a JSON with all keys and values of that path. So, we can get all values from the database and updated the GPIO states*/ if (data.dataTypeEnum() == fb_esp_rtdb_data_type_json){ FirebaseJson json = data.to<FirebaseJson>(); // To iterate all values in Json object size_t count = json.iteratorBegin(); Serial.println("\n---------"); for (size_t i = 0; i < count; i++){ FirebaseJson::IteratorValue value = json.valueAt(i); int gpio = value.key.toInt(); int state = value.value.toInt(); Serial.print("STATE: "); Serial.println(state); Serial.print("GPIO:"); Serial.println(gpio); digitalWrite(gpio, state); Serial.printf("Name: %s, Value: %s, Type: %s\n", value.key.c_str(), value.value.c_str(), value.type == FirebaseJson::JSON_OBJECT ? "object" : "array"); } Serial.println(); json.iteratorEnd(); // required for free the used memory in iteration (node data collection) } //This is the size of stream payload received (current and max value) //Max payload size is the payload size under the stream path since the stream connected //and read once and will not update until stream reconnection takes place. //This max value will be zero as no payload received in case of ESP8266 which //BearSSL reserved Rx buffer size is less than the actual stream payload. Serial.printf("Received stream payload size: %d (Max. %d)\n\n", data.payloadLength(), data.maxPayloadLength()); } void streamTimeoutCallback(bool timeout){ if (timeout) Serial.println("stream timeout, resuming...\n"); if (!stream.httpConnected()) Serial.printf("error code: %d, reason: %s\n\n", stream.httpCode(), stream.errorReason().c_str()); } void setup(){ Serial.begin(115200); initWiFi(); // Initialize Outputs pinMode(output1, OUTPUT); pinMode(output2, OUTPUT); pinMode(output3, OUTPUT); // Assign the api key (required) config.api_key = API_KEY; // Assign the user sign in credentials auth.user.email = USER_EMAIL; auth.user.password = USER_PASSWORD; // Assign the RTDB URL (required) config.database_url = DATABASE_URL; Firebase.reconnectWiFi(true); // Assign the callback function for the long running token generation task */ config.token_status_callback = tokenStatusCallback; //see addons/TokenHelper.h // Assign the maximum retry of token generation config.max_token_generation_retry = 5; // Initialize the library with the Firebase authen and config Firebase.begin(&config, &auth); // Streaming (whenever data changes on a path) // Begin stream on a database path --> board1/outputs/digital if (!Firebase.RTDB.beginStream(&stream, listenerPath.c_str())) Serial.printf("stream begin error, %s\n\n", stream.errorReason().c_str()); // Assign a calback function to run when it detects changes on the database Firebase.RTDB.setStreamCallback(&stream, streamCallback, streamTimeoutCallback); delay(2000); } void loop(){ if (Firebase.isTokenExpired()){ Firebase.refreshToken(&config); Serial.println("Refresh token"); } } View raw code

How the Code Works

Continue reading to learn how the code works or skip to the demonstration section.

Include Libraries

First, include the required libraries. This code is also compatible with the ESP8266. #include <Arduino.h> #if defined(ESP32) #include <WiFi.h> #elif defined(ESP8266) #include <ESP8266WiFi.h> #endif #include <Firebase_ESP_Client.h> // Provide the token generation process info. #include "addons/TokenHelper.h" // Provide the RTDB payload printing info and other helper functions. #include "addons/RTDBHelper.h"

Network Credentials

Include your network credentials in the following lines so that your boards can connect to the internet using your local network. // Insert your network credentials #define WIFI_SSID "REPLACE_WITH_YOUR_SSID" #define WIFI_PASSWORD "REPLACE_WITH_YOUR_PASSWORD"

Firebase Project API Key, Firebase User, and Database URL

Insert your Firebase project API keythe one you've gotten in this section. #define API_KEY "REPLACE_WITH_YOUR_PROJECT_API_KEY" Insert the authorized email and the corresponding passwordthese are the details of the user you've added in this section. // Insert Authorized Email and Corresponding Password #define USER_EMAIL "REPLACE_WITH_THE_USER_EMAIL" #define USER_PASSWORD "REPLACE_WITH_THE_USER_PASSWORD" Insert your database URL in the following line: // Insert RTDB URLefine the RTDB URL #define DATABASE_URL "REPLACE_WITH_YOUR_DATABASE_URL"

Firebase Objects and Other Variables

First, you need to create a FirebaseData object (we called it stream) to handle the data when there's a change on a specific database path. FirebaseData stream; The next line defines a FirebaseAuth object needed for authentication. FirebaseAuth auth; Finally, the following line defines a FirebaseConfig object required for configuration data. FirebaseConfig config;

Database Path

Then, create a variable that saves the database path where we'll be listening for changes. Taking into account the database structure we created previously, the listener database path should be as follows: String listenerPath = "board1/outputs/digital/"; If you want to add more boards, then you just need to change the listener path accordingly. Create variables for the outputs you'll control. In our case, we're controlling GPIOs 12, 13, and 14. You can control any other ESP32 GPIOs (you'll also need to change the database nodes): const int output1 = 12; const int output2 = 13; const int output3 = 14;

initWifi()

The iniWifi() function connects the ESP32 to your local network. We'll call it later in the setup() to initialize Wi-Fi. // Initialize WiFi void initWiFi() { WiFi.begin(WIFI_SSID, WIFI_PASSWORD); Serial.print("Connecting to WiFi .."); while (WiFi.status() != WL_CONNECTED) { Serial.print('.'); delay(1000); } Serial.println(WiFi.localIP()); Serial.println(); }

setup()

Let's now jump to the setup(). We'll take a look at the streamCallback() function later. Initialize the Serial Monitor: Serial.begin(115200); Call the initiWiFi() function we created previously, to connect your board to your local network. initWiFi(); Initialize the GPIOs as outputs. // Initialize Outputs pinMode(output1, OUTPUT); pinMode(output2, OUTPUT); pinMode(output3, OUTPUT); Assign the API key to the Firebase configuration. config.api_key = API_KEY; The following lines assign the email and password to the Firebase authentication object. auth.user.email = USER_EMAIL; auth.user.password = USER_PASSWORD; Assign the database URL to the Firebase configuration object. config.database_url = DATABASE_URL; Add the following to the configuration object. // Assign the callback function for the long running token generation task config.token_status_callback = tokenStatusCallback; //see addons/TokenHelper.h // Assign the maximum retry of token generation config.max_token_generation_retry = 5; Initialize the Firebase library (authenticate) with the configuration and authentication settings we defined earlier. // Initialize the library with the Firebase authen and config Firebase.begin(&config, &auth);

Stream Database Listening for Changes

Listening for changes on the database works with callback functions. This means, that when a change is detected on the database, a callback function will run. The following line of code starts a stream to listen for changes on the listenerPath. if (!Firebase.RTDB.beginStream(&stream, listenerPath.c_str())) Then, set a callback function to be triggered whenever there's a change on the listenerPaththe streamCallback function. Firebase.RTDB.setStreamCallback(&stream, streamCallback, streamTimeoutCallback);

streamCallback() function

Let's now take a look at the streamCallback function created previously. When the streamCallback() function is triggered, an object called data of type FirebaseStream is passed automatically as an argument to that function. From that object, we can get the stream path, the data path (the full database path where the change occurred, including the value of the lowest child), the data type of that value, and the event type that triggered the stream. void streamCallback(FirebaseStream data){ Serial.printf("stream path, %s\nevent path, %s\ndata type, %s\nevent type, %s\n\n", data.streamPath().c_str(), data.dataPath().c_str(), data.dataType().c_str(), data.eventType().c_str()); printResult(data); //see addons/RTDBHelper.h Serial.println(); // Get the path that triggered the function String streamPath = String(data.dataPath()); Then, from the obtained information, the ESP can run certain tasks, like updating the GPIO states. When you change the value of the GPIO states on the database, the data returned by that event is an integer (in this case, 0 or 1). So, first, we check if the response is an integer: if (data.dataTypeEnum() == fb_esp_rtdb_data_type_integer){ If it is, the event path corresponds to the GPIO node path, for example /12. From that path, we can get the GPIO number we want to change, we just need to cut the / from the string. String gpio = streamPath.substring(1); To better understand this, you can take a look at the following screenshot. It shows what you get when there's a change in the GPIOs states. We can get the value of the returned data as follows (knowing beforehand that it is an integer): int state = data.intData(); Then, we can simply call the digitalWrite() function and pass as arguments the GPIO number and the state to keep the ESP32 output states updated. digitalWrite(gpio.toInt(), state); When the ESP first connects to the database, it is triggered on the root(/) path and returns a JSON object with all child nodes. So, we can get all values from the database and update the ESP32 GPIOs when it first runs. This is also useful because if the ESP32 resets, it will always receive this JSON object first, and will be able to update all GPIOs. As you can see from the previous screenshot, the JSON object it receives looks as follows (it might be different depending on the GPIO states): { "12": 0, "13": 0, "14": 0 } When this happens, the returned data is of type JSON. So, we can get it with the following if statement: if (data.dataTypeEnum() == fb_esp_rtdb_data_type_json){ We can convert the returned data to a FirebaseJSON object: FirebaseJson json = data.to<FirebaseJson>(); Then, we can iterate through the whole JSON object and get the keys (GPIOs) and corresponding values (GPIO states). In each iteration, we save the GPIO on the gpio variable and its corresponding state on the state variable. Then, we call the digitalWrite() function to update its state. // To iterate all values in Json object size_t count = json.iteratorBegin(); Serial.println("\n---------"); for (size_t i = 0; i < count; i++){ FirebaseJson::IteratorValue value = json.valueAt(i); int gpio = value.key.toInt(); int state = value.value.toInt(); Serial.print("STATE: "); Serial.println(state); Serial.print("GPIO:"); Serial.println(gpio); digitalWrite(gpio, state); Serial.printf("Name: %s, Value: %s, Type: %s\n", value.key.c_str(), value.value.c_str(), value.type == FirebaseJson::JSON_OBJECT ? "object" : "array"); } Serial.println(); json.iteratorEnd(); // required for free the used memory in iteration (node data collection) This runs through all keys and values allowing us to update all GPIOs. Because all our code works with callback functions, we don't need to place anything on the loop() besides the lines to refresh the Firebase token. void loop(){ if (Firebase.isTokenExpired()){ Firebase.refreshToken(&config); Serial.println("Refresh token"); } } If the ESP32 needs to perform other tasks, you can add them in the loop(). In our case, this example only listens for database changes.

Demonstration

After inserting all required credentials, upload the code to your board. After uploading, open the Serial Monitor at a baud rate of 115200 and reset the board. You should get something as shown below. As you can see, when the ESP first runs, it gets a JSON object with all GPIO states. { "12": 0, "13": 0, "14": 0 } Then, go to the Firebase Realtime Database on the Firebase console. Manually change the GPIO states (either 0 or 1). After inserting a new value, press Enter. Right after, you'll see on the Serial Monitor that the ESP32 detected the changes. And it will update the GPIO states and light up the LEDs almost instantaneously. Then, if you reset your board (press the RST button or remove and apply power again), when it restarts, it will get the latest GPIO states from the database and updates them right away.

Taking it Further Add More Boards

You can take this project further and add more boards. To do that, create new database nodes for a second board. You can add ESP32 or ESP8266 boards. You can download the following JSON file and import it to your database, and it will create nodes for two boards: Download JSON file After uploading the JSON file, the database will look as follows: Now, you can upload the same code to the new board (it is compatible with the ESP32 and ESP8266). But don't forget to change the listening path. It should be: String listenerPath = "board2/outputs/digital/"; Now, you can control both boards by changing the GPIO states on the database. In Part 2, we'll create a Firebase web app so that you have a nice interface to control your GPIOs from anywhere without having to use the Firebase console and change the database manually: PART 2: Control ESP32/ESP8266 GPIOs from Anywhere (Firebase Web App)

Wrapping Up

In this tutorial, you learned how to use the Firebase Realtime Database to save the ESP GPIO states. You also learned how to program the ESP32 to listen for database changes. Whenever a change is detected, we updated the corresponding GPIO states. You can change the code so that the ESP listens for any other data saved on the database, not only GPIO states. Because you can access the Firebase Realtime Database from anywhere, you can control your boards from anywhere too. This is great for IoT projects. In Part 2, we'll create a web app to control your GPIOs from anywhere, without the need to login manually on the Firebase console: PART 2: Control ESP32/ESP8266 GPIOs from Anywhere (Firebase Web App) If you like Firebase projects, please take a look at our new eBook. We're sure you'll like it: Firebase Web App with ESP32 and ESP8266

WiFiMulti: Connect to the Strongest Wi-Fi Network (from a list of networks)

Learn how to use WiFiMulti with the ESP32. It allows you to register multiple networks (SSID/password combinations). The ESP32 will connect to the Wi-Fi network with the strongest signal (RSSI). If the connection is lost, it will connect to the next network on the list. Using WiFiMulti in your ESP32 IoT projects is useful if your board can have access to more than one Wi-Fi network. Implementing this feature in your projects is very simple and improves your projects significantly.

ESP32 with WiFiMulti

You can easily add WiFiMulti to your ESP32 projects with just a few lines of code. You can find an example in your Arduino IDE. With an ESP32 board select (Tools > Board), go to File > Examples > WiFi > WifiMulti. Here are the essential steps to use WiFiMulti with the ESP32.

Include Libraries

First, you need to include both WiFi.h and WiFiMulti.h libraries. #include <WiFi.h> #include <WiFiMulti.h>

WiFiMulti Object

Then, you need to create a WiFiMulti object: WiFiMulti wifiMulti;

Add List of Networks

Then, in the setup(), use the addAp() method on the wifiMulti object to add a network. The addAP() method accepts as arguments the network SSID and password. You should add at least one network. wifiMulti.addAP("ssid_from_AP_1", "your_password_for_AP_1"); wifiMulti.addAP("ssid_from_AP_2", "your_password_for_AP_2"); wifiMulti.addAP("ssid_from_AP_3", "your_password_for_AP_3");

Connect to Wi-Fi

Finally, connect to Wi-Fi using the run() method. You can also print a message in case the Wi-Fi is disconnected. if(wifiMulti.run() != WL_CONNECTED) { Serial.println("WiFi not connected!"); delay(1000); } You can run this snippet on the loop() section and if the ESP32 gets disconnected from a Wi-Fi network, it will automatically try to connect to the next strongest network on the list.

ESP32 with WiFiMulti Example

For you to understand how WiFiMulti works with the ESP32, we created a simple example that does the following: scans for available wi-fi networks and prints their RSSI (so that you can check that the ESP32 is actually connecting to the strongest network on the list); connects to the strongest wi-fi network from a list of provided networks; in case it loses connection with the network, it will automatically connect to the next strongest network on the list. To test this, you can copy the following code to your Arduino IDE. It is based on the WiFiScan and WiFiMulti examples provided in the Arduino core examples for the ESP32. /* * Based on the following examples: * WiFi > WiFiMulti: https://github.com/espressif/arduino-esp32/blob/master/libraries/WiFi/examples/WiFiMulti/WiFiMulti.ino * WiFi > WiFiScan: https://github.com/espressif/arduino-esp32/blob/master/libraries/WiFi/examples/WiFiScan/WiFiScan.ino * Complete project details at our blog: https://RandomNerdTutorials.com/ * */ #include <WiFi.h> #include <WiFiMulti.h> WiFiMulti wifiMulti; // WiFi connect timeout per AP. Increase when connecting takes longer. const uint32_t connectTimeoutMs = 10000; void setup(){ Serial.begin(115200); delay(10); WiFi.mode(WIFI_STA); // Add list of wifi networks wifiMulti.addAP("ssid_from_AP_1", "your_password_for_AP_1"); wifiMulti.addAP("ssid_from_AP_2", "your_password_for_AP_2"); wifiMulti.addAP("ssid_from_AP_3", "your_password_for_AP_3"); // WiFi.scanNetworks will return the number of networks found int n = WiFi.scanNetworks(); Serial.println("scan done"); if (n == 0) { Serial.println("no networks found"); } else { Serial.print(n); Serial.println(" networks found"); for (int i = 0; i < n; ++i) { // Print SSID and RSSI for each network found Serial.print(i + 1); Serial.print(": "); Serial.print(WiFi.SSID(i)); Serial.print(" ("); Serial.print(WiFi.RSSI(i)); Serial.print(")"); Serial.println((WiFi.encryptionType(i) == WIFI_AUTH_OPEN)?" ":"*"); delay(10); } } // Connect to Wi-Fi using wifiMulti (connects to the SSID with strongest connection) Serial.println("Connecting Wifi..."); if(wifiMulti.run() == WL_CONNECTED) { Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); } } void loop(){ //if the connection to the stongest hotstop is lost, it will connect to the next network on the list if (wifiMulti.run(connectTimeoutMs) == WL_CONNECTED) { Serial.print("WiFi connected: "); Serial.print(WiFi.SSID()); Serial.print(" "); Serial.println(WiFi.RSSI()); } else { Serial.println("WiFi not connected!"); } delay(1000); } View raw code Don't forget to add a list of networks on the following lines. You can multiply those lines to add more networks. wifiMulti.addAP("ssid_from_AP_1", "your_password_for_AP_1"); wifiMulti.addAP("ssid_from_AP_2", "your_password_for_AP_2"); wifiMulti.addAP("ssid_from_AP_3", "your_password_for_AP_3"); Note: if you want to test this project, but at the moment, you only have access to one network, you can create a hotspot with your smartphone and add the hotspot name and password to the list of available networks. I tested this with my iPhone and it worked perfectly (you may need to remove spaces and special characters from the hotspot name).

ESP32 with WiFiMulti Demonstration

After adding a list of networks to your code, you can upload it to your ESP32. Open the Serial Monitor at a baud rate of 115200 and press the ESP32 RST button to restart the board. First, it will show a list of nearby networks and corresponding RSSI. In my case, I have access to the first and third networks. In my case, the ESP32 connects to the iPhone network which is the strongest on the list (an RSSI closer to zero means a stronger signal). If I remove the iPhone hotspot, the connection will be lost and it will connect to the next strongest network on the list.

Wrapping Up

In this tutorial, you learned how to use WiFiMulti with the ESP32 to add a list of networks that the ESP32 can connect to. It will connect to the network with the strongest signal (RSSI). If it loses connection with that network, it will automatically try to connect to the next network on the list. We hope you find this tutorial useful. We have other tutorials related to Wi-Fi functions with the ESP32 that you may find useful: ESP32 Useful Wi-Fi Library Functions (Arduino IDE) [SOLVED] Reconnect ESP32 to Wi-Fi Network After Lost Connection

with Load Cell and HX711 Amplifier (Digital Scale)

In this guide, you'll learn how to create a scale with the ESP32 using a load cell and the HX711 amplifier. First, you'll learn how to wire the load cell and the HX711 amplifier to the ESP32 to build a scale. Then, we'll show you how to calibrate the scale, and a simple example to get the weight of objects. Later, we'll also add a display to show the measurements and a button to tare the scale. Table of Contents In this tutorial, we'll cover the following topics: Introducing Load Cells (Strain Gauges) HX711 Amplifier Setting Up Load Cell Wiring Load Cell and HX711 Amplifier to the ESP32 Installing HX711 Library Calibrating the Scale Weighting Objects Code Digital Scale with ESP32

Introducing Load Cells

A load cell converts a force into an electrical signal that can be measured. The electrical signal changes proportionally to the force applied. There are different types of load cells: strain gauges, pneumatic, and hydraulic. In this tutorial, we'll cover strain gauge load cells. Strain gauge load cells are composed of a metal bar with attached strain gauges (under the white glue in the picture above). A strain gauge is an electrical sensor that measures force or strain on an object. The resistance of the strain gauges varies when an external force is applied to an object, which results in a deformation of the object's shape (in this case, the metal bar). The change of the resistance is proportional to the load applied, which allows us to calculate the weight of objects. Usually, load cells have four strain gauges hooked up in a Wheatstone bridge (as shown below) that allow us to get accurate resistance measurements. For a more detailed explanation of how strain gauges work, read this article. The wires coming from the load cell usually have the following colors: Red: VCC (E+) Black: GND (E-) White: Output (A-) Green: Output + (A+) Applications Strain gauge load cells can be used in a wide variety of applications. For example: check if an object's weight changes over time; measure the weight of an object; detect the presence of an object; estimate a container's liquid level; etc. Because the changes in strain when weighting objects are so small, we need an amplifier. The load cell we're using is usually sold together with an HX711 amplifier. So, that's the amplifier we'll use.

HX711 Amplifier

The HX711 amplifier is a breakout board that allows you to easily read load cells to measure weight. You wire the load cell wires on one side, and the microcontroller on the other side. The HX711 communicates with the microcontroller using two-wire interface (Clock and Data). You need to solder header pins on the GND, DT, SCK, and VCC pins to connect to the ESP32. I soldered the load cell wires directly to the E+, E-, A-, and A+ pins. The load cell wires were very thin and fragile, be careful when soldering to not damage the wires. For more information about the HX711 amplifier, you can consult the HX711 datasheet.

Setting Up the Load Cell

Our load cell kit came with two acrylic plates and some screws to set up the load cell as a scale. You can use wood plates or 3D-print your own plates. You should attach the plates to the load cell in a way that creates a strain between the opposite ends of the metal bar. The bottom plate holds the load cell, and the upper plate is where you place the objects. The following figure shows what my load cell with the acrylic plates looks like.

Where to Buy Load Cell with HX711?

You can check the load cell with the HX711 on Maker Advisor to find the best price (with or without acrylic plates included). There are load cells with different measurement ranges. The most common maximum weights are 1kg, 5kg, 10kg, and 20kg. Load Cell with HX711 Amplifier You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Wiring Load Cell and HX711 Amplifier to the ESP32

The HX711 amplifier communicates via two-wire interface. You can connect it to any GPIOs of your chosen microcontroller. We're connecting the data pin (DT) to GPIO 16 and the clock pin (CLK) to GPIO 4. You can use any other suitable pins (check the ESP32 pinout guide). Follow the next table or schematic diagram to wire the load cell to the ESP32 board.
Load Cell HX711 HX711 ESP32
Red (E+) E+ GND GND
Black (E-) E- DT GPIO 16
White (A-) A- SCK GPIO 4
Green (A+) A+ VCC 3.3V

Installing the HX711 Library

There are several different libraries to get measurements from a load cell using the HX711 amplifier. We'll use the HX711 library by bodge. It is compatible with the ESP32, ESP8266, and Arduino.

Arduino IDE

Follow the next instructions to install the library if you're using Arduino IDE.
    Open Arduino IDE and go to Sketch > Include Library > Manage Libraries. Search for HX711 Arduino Library and install the library by Bogdan Necula.

VS Code with PlatformIO

If you're using VS Code with the PlatformIO extension to program your boards, follow the next instructions.
    After creating a new project on PlatformIO for your board, go to the PIO Home (click on the house icon on the bottom bar). Then, click on Libraries. Search for HX711 and select the Library by bodge. Then, click on Add to Project and select the project you're working on.
Now, if you go to your project folder and open the platformio.ini file, there should be a line to include the library as follows: lib_deps = bogde/HX711@^0.7.5 Also add the following line to change the Serial Monitor speed to 115200: monitor_speed = 115200

Calibrating the Scale (ESP32 with Load Cell)

At this time, we assume you have wired the load cell to the HX711 amplifier and the amplifier to the ESP32. You should also have your scale set up (two plates wired on opposite ends on the load cell), and have installed the HX711 library. Before getting the weight of objects, you need to calibrate your load cell first by getting the calibration factor. Your calibration factor will be different than mine, so you shouldn't skip this section. 1) Prepare an object with a known weight. I used my kitchen scale and weighed a glass with water (300g). 2) Upload the following code to your ESP32. We wrote the following code taking into account the instructions to calibrate the load cell provided by the library documentation. /* Rui Santos Complete project details at https://RandomNerdTutorials.com/esp32-load-cell-hx711/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ // Calibrating the load cell #include <Arduino.h> #include "soc/rtc.h" #include "HX711.h" // HX711 circuit wiring const int LOADCELL_DOUT_PIN = 16; const int LOADCELL_SCK_PIN = 4; HX711 scale; void setup() { Serial.begin(115200); rtc_clk_cpu_freq_set(RTC_CPU_FREQ_80M); scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN); } void loop() { if (scale.is_ready()) { scale.set_scale(); Serial.println("Tare... remove any weights from the scale."); delay(5000); scale.tare(); Serial.println("Tare done..."); Serial.print("Place a known weight on the scale..."); delay(5000); long reading = scale.get_units(10); Serial.print("Result: "); Serial.println(reading); } else { Serial.println("HX711 not found."); } delay(1000); } //calibration factor will be the (reading)/(known weight) View raw code 3) After uploading, open the Serial Monitor at a baud rate of 115200 and reset the ESP32 board. 4) Follow the instructions on the Serial Monitor: remove any weights from the scale (it will tare automatically). Then, place an object with a known weight on the scale and wait until you get a value. 5) Calculate your calibration factor using the formula: calibration factor = (reading)/(known weight) In our case, the reading is -141449. The known weight is 300g, so our calibration factor will be: -141449/300 = -471.497. calibration factor = -141449/300 = -471.497 Save your calibration factor because you'll need it later. Yours will be different than ours. Because the output of the sensor is proportional to the force applied to the load cell, you can calibrate your scale using whatever unit makes sense for you. I used grams, but you can use pounds, kilograms, or even pieces of cat food (as in this Andreas Spiess video).

Weighting Objects (ESP32 with Load Cell)

Now that you know your calibration factor, you can use your load cell to weight objects. Start by weighing objects with a known weight and repeat the calibration process if the values are not accurate. Copy the following code to your Arduino IDE. Before uploading it to your board, don't forget to insert your calibration factor in line 43/44 of the code. The following code is the example provided by the library that demonstrates the use of most of its functions. /* * Complete project details at https://RandomNerdTutorials.com/esp32-load-cell-hx711/ * * HX711 library for Arduino - example file * https://github.com/bogde/HX711 * * MIT License * (c) 2018 Bogdan Necula * **/ #include <Arduino.h> #include "HX711.h" #include "soc/rtc.h" // HX711 circuit wiring const int LOADCELL_DOUT_PIN = 16; const int LOADCELL_SCK_PIN = 4; HX711 scale; void setup() { Serial.begin(115200); rtc_clk_cpu_freq_set(RTC_CPU_FREQ_80M); Serial.println("HX711 Demo"); Serial.println("Initializing the scale"); scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN); Serial.println("Before setting up the scale:"); Serial.print("read: \t\t"); Serial.println(scale.read()); // print a raw reading from the ADC Serial.print("read average: \t\t"); Serial.println(scale.read_average(20)); // print the average of 20 readings from the ADC Serial.print("get value: \t\t"); Serial.println(scale.get_value(5)); // print the average of 5 readings from the ADC minus the tare weight (not set yet) Serial.print("get units: \t\t"); Serial.println(scale.get_units(5), 1); // print the average of 5 readings from the ADC minus tare weight (not set) divided // by the SCALE parameter (not set yet) scale.set_scale(INSERT YOUR CALIBRATION FACTOR); //scale.set_scale(-471.497); // this value is obtained by calibrating the scale with known weights; see the README for details scale.tare(); // reset the scale to 0 Serial.println("After setting up the scale:"); Serial.print("read: \t\t"); Serial.println(scale.read()); // print a raw reading from the ADC Serial.print("read average: \t\t"); Serial.println(scale.read_average(20)); // print the average of 20 readings from the ADC Serial.print("get value: \t\t"); Serial.println(scale.get_value(5)); // print the average of 5 readings from the ADC minus the tare weight, set with tare() Serial.print("get units: \t\t"); Serial.println(scale.get_units(5), 1); // print the average of 5 readings from the ADC minus tare weight, divided // by the SCALE parameter set with set_scale Serial.println("Readings:"); } void loop() { Serial.print("one reading:\t"); Serial.print(scale.get_units(), 1); Serial.print("\t| average:\t"); Serial.println(scale.get_units(10), 5); scale.power_down(); // put the ADC in sleep mode delay(5000); scale.power_up(); } View raw code

How the Code Works

Start by including the required libraries. We've included Arduino.h in case you're using PlatformIO instead of Arduino IDE. #include <Arduino.h> #include "HX711.h" #include "soc/rtc.h" Note: we've read in some places that you need to slow down the ESP32 processor because of the HX711 frequency. I'm not sure if this is really needed or not. We've experimented with and without slowing down and everything worked fine in both scenarios. Nonetheless, we've added that option to the code. You can always remove it. To do that you need to include soc/rtc.h. The following lines define the GPIOs you'll use to connect to the HX711 amplifier. We chose GPIOs 16 and 4. You can use any other suitable GPIOs. const int LOADCELL_DOUT_PIN = 16; const int LOADCELL_SCK_PIN = 4; Then, create an instance of the HX711 library called scale that you'll use later on to get the measurements. HX711 scale;

setup()

In the setup(), initialize the Serial monitor. Serial.begin(115200); Slow down the ESP32 processor. rtc_clk_cpu_freq_set(RTC_CPU_FREQ_80M); Initialize the load cell by calling the begin() method on the scale object and passing the GPIOs as arguments. scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN); Then, it calls several methods that you can use to get readings using the library. read(): gets a raw reading from the sensor read_average(number of readings): gets the average of the latest defined number of readings get_value(number of readings): gets the average of the last defined number of readings minus the tare weight; get_units(number of readings): gets the average of the last defined number of readings minus the tare weight divided by the calibration factor this will output a reading in your desired units. Serial.println("Before setting up the scale:"); Serial.print("read: \t\t"); Serial.println(scale.read()); // print a raw reading from the ADC Serial.print("read average: \t\t"); Serial.println(scale.read_average(20)); // print the average of 20 readings from the ADC Serial.print("get value: \t\t"); Serial.println(scale.get_value(5)); // print the average of 5 readings from the ADC minus the tare weight (not set yet) Serial.print("get units: \t\t"); Serial.println(scale.get_units(5), 1); // print the average of 5 readings from the ADC minus tare weight (not set) divided // by the SCALE parameter (not set yet) In the following line, don't forget to insert your calibration factor. It uses the set_scale() method. scale.set_scale(INSERT YOUR CALIBRATION FACTOR) Then, call the tare() method to tare the scale. scale.tare(); // reset the scale to 0 After this setup, the scale should be ready to get accurate readings in your desired unit. The example calls the same previous methods so that you can see the difference before and after setting up the scale. Serial.print("read: \t\t"); Serial.println(scale.read()); // print a raw reading from the ADC Serial.print("read average: \t\t"); Serial.println(scale.read_average(20)); // print the average of 20 readings from the ADC Serial.print("get value: \t\t"); Serial.println(scale.get_value(5)); // print the average of 5 readings from the ADC minus the tare weight, set with tare() Serial.print("get units: \t\t"); Serial.println(scale.get_units(5), 1); // print the average of 5 readings from the ADC minus tare weight, divided // by the SCALE parameter set with set_scale

loop()

In the loop(), the example calls the get_units() method in two different ways: to get one single readings (without any parameters) and to get the average of the last 10 readings. Serial.print("one reading:\t"); Serial.print(scale.get_units(), 1); Serial.print("\t| average:\t"); Serial.println(scale.get_units(10), 5); It shuts down the ADC that reads the sensor by using the power_down() method. Then, it waits for 5 seconds, powers up the ADC (power_up()), and the loop() repeats. So, you'll get new readings on the Serial Monitor every 5 seconds. scale.power_down(); // put the ADC in sleep mode delay(5000); scale.power_up();

Demonstration

Upload the code to your ESP32 board. After uploading, open the Serial Monitor at a baud rate of 115200. Let the code run a few seconds so that it has time to set up the scale (you'll see the message on the Serial Monitor). Then, place any object on the scale to measure it and you'll get the results on the Serial Monitor. I experimented with several objects and compared them against the value on my kitchen scale, and the results were the same. So, I can say that my ESP32 scale is at least as accurate as my kitchen scale.

Digital Scale with ESP32

In this section, we'll create a simple digital scale with the ESP32. We'll add an OLED display to show the results and a pushbutton to tare the scale.

Parts Required

Here's a list of the parts required for this project: ESP32 (read Best ESP32 Development Boards) Load Cell with HX711 Amplifier I2C SSD1306 OLED Display Pushbutton 10K Ohm Resistor Breadboard Jumper Wires

Schematic Diagram

Add an OLED display and a pushbutton to your previous circuit on the following pins:
OLED Display ESP32
VCC 3.3V or 5V*
GND GND
SDA GPIO 21
SCL GPIO 22
*connect to 3.3V or 5V depending on the model. Not familiar with the OLED display? Read: ESP32 OLED Display with Arduino IDE Wire the pushbutton via a 10kOhm pull-down resistor to GPIO 19. The other lead of the pushbutton should be connected to 3.3V. You can use any other suitable GPIO (check the ESP32 pinout guide). You can follow the next schematic diagram to wire your parts.

ESP32 Digital Scale Code

For simplicity, we'll handle the pushbutton using a simple library that detects button presses with debouncing (so we don't need to worry about that in our code). To write to the OLED display, we'll use the Adafruit SSD1306 and Adafruit GFX libraries.

Pushbutton Library

There are many libraries with many functionalities to handle pushbuttons. We'll use the pushbutton library by polulu. It is a simple library but comes with everything we need for this project. In your Arduino IDE, go to Sketch > Include Library > Manage Libraries and search for pushbutton. Install the pushbutton library by polulu. Alternatively, if you don't want to use the library you can add the debounce code yourself (which is not difficult). For a debounce code example, in the Arduino IDE, you can go to File > Examples > Digital > Debounce.

OLED Libraries

We'll use the following libraries to control the OLED display. Make sure you have these libraries installed: Adafruit_SSD1306 library Adafruit_GFX library You can install the libraries using the Arduino Library Manager. Go to Sketch > Include Library > Manage Libraries and search for the library name.

Installing Libraries PlatformIO

If you're using VS Code with the PlatformIO extension, follow the next steps to install the library:
    After creating a new project on PlatformIO for your board, go to the PIO Home (click on the house icon on the bottom bar). Then, click on Libraries. Search for pushbutton and select the Pushbutton library by Polulu. Then, click on Add to Project and select the project you're working on. Repeat the process for the Adafruit SSD1306 and Adafruit GFX libraries. Also, don't forget to add the HX711 library too.
In your platformio.ini file, you should have the following lines that include all the required libraries (also change the Serial Monitor speed to 115200). monitor_speed = 115200 lib_deps = bogde/HX711@^0.7.5 pololu/Pushbutton@^2.0.0 adafruit/Adafruit SSD1306@^2.4.6 adafruit/Adafruit GFX Library@^1.10.10

Code

Copy the following code to your Arduino IDE. Before uploading it to the ESP32, you need to insert your calibration factor (obtained previously). /* Rui Santos Complete project details at https://RandomNerdTutorials.com/esp32-load-cell-hx711/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ // Library HX711 by Bogdan Necula: https://github.com/bogde/HX711 // Library: pushbutton by polulu: https://github.com/pololu/pushbutton-arduino #include <Arduino.h> #include "HX711.h" #include "soc/rtc.h" #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include <Pushbutton.h> // HX711 circuit wiring const int LOADCELL_DOUT_PIN = 16; const int LOADCELL_SCK_PIN = 4; HX711 scale; int reading; int lastReading; //REPLACE WITH YOUR CALIBRATION FACTOR #define CALIBRATION_FACTOR -471.497 //OLED Display #define SCREEN_WIDTH 128 // OLED display width, in pixels #define SCREEN_HEIGHT 64 // OLED display height, in pixels // Declaration for an SSD1306 display connected to I2C (SDA, SCL pins) #define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); //Button #define BUTTON_PIN 19 Pushbutton button(BUTTON_PIN); void displayWeight(int weight){ display.clearDisplay(); display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(0, 10); // Display static text display.println("Weight:"); display.display(); display.setCursor(0, 30); display.setTextSize(2); display.print(weight); display.print(" "); display.print("g"); display.display(); } void setup() { Serial.begin(115200); rtc_clk_cpu_freq_set(RTC_CPU_FREQ_80M); if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println(F("SSD1306 allocation failed")); for(;;); } delay(2000); display.clearDisplay(); display.setTextColor(WHITE); Serial.println("Initializing the scale"); scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN); scale.set_scale(CALIBRATION_FACTOR); // this value is obtained by calibrating the scale with known weights; see the README for details scale.tare(); // reset the scale to 0 } void loop() { if (button.getSingleDebouncedPress()){ Serial.print("tare..."); scale.tare(); } if (scale.wait_ready_timeout(200)) { reading = round(scale.get_units()); Serial.print("Weight: "); Serial.println(reading); if (reading != lastReading){ displayWeight(reading); } lastReading = reading; } else { Serial.println("HX711 not found."); } } View raw code

How the Code Works

Start by including the required libraries: #include <Arduino.h> #include "HX711.h" #include "soc/rtc.h" #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include <Pushbutton.h> Define the pins for the HX711 (load cell)we're using the same as previous examples: // HX711 circuit wiring const int LOADCELL_DOUT_PIN = 16; const int LOADCELL_SCK_PIN = 4; Create an HX711 instance called scale. HX711 scale; The following variables will hold the current weight reading and the last weight reading. We only want to update the OLED display in case there's a new reading, so that's why we need these two variables. Additionally, we don't want to measure decimals of grams which will make the scale too sensitive for our applicationthat's why these variables are integers. If you need decimals in your measurements, you can define float variables instead. int reading; int lastReading; Don't forget to replace the next value with your calibration factor. In my case, that line of code looks as follows (my value is negative): #define CALIBRATION_FACTOR -471.497 Next, we need to define the OLED width and height: #define SCREEN_WIDTH 128 // OLED display width, in pixels #define SCREEN_HEIGHT 64 // OLED display height, in pixels And create an instance of the Adafruit_SSD1306 library called display. Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); Define the GPIO you'll use to read the button and create a Pushbutton object called button on that pin. #define BUTTON_PIN 19 Pushbutton button(BUTTON_PIN);

displayWeight() function

We created a function called displayWeight() that accepts as arguments the weight you want to display on the OLED. void displayWeight(int weight){ display.clearDisplay(); display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(0, 10); // Display static text display.println("Weight:"); display.display(); display.setCursor(0, 30); display.setTextSize(2); display.print(weight); display.print(" "); display.print("g"); display.display(); } Not familiar with the OLED display? Read: ESP32 OLED Display with Arduino IDE.

setup()

In the setup(), initialize the Serial Monitor. Serial.begin(115200); Initialize the OLED display: if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println(F("SSD1306 allocation failed")); for(;;); } delay(2000); display.clearDisplay(); display.setTextColor(WHITE); And finally, initialize the load cell: Serial.println("Initializing the scale"); scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN); scale.set_scale(CALIBRATION_FACTOR); // this value is obtained by calibrating the scale with known weights scale.tare(); // reset the scale to 0

loop()

The pushbutton library allows us to wait for an event in case of a pushbutton press or pushbutton release. In this case, we check whether the pushbutton was pushed using the getSingleDebouncePress() method and call the tare() function if the button was pressed. if (button.getSingleDebouncedPress()){ Serial.print("tare..."); scale.tare(); } The HX711 provides a non-blocking method to get readings. It defines a maximum timeout to wait for the hardware to be initialized and doesn't block your code in case the scale gets disconnected or in case of hardware failures. if (scale.wait_ready_timeout(200)) { reading = round(scale.get_units()); Serial.print("Weight: "); Serial.println(reading); In the loop(), we are constantly getting new readings and checking them against the latest reading. If we got a new measurement, we call the displayWeight() function to update the OLED display. if (reading != lastReading){ displayWeight(reading); }

Demonstration

After uploading the code to your board, you can start weighing objects with your load cell. The readings will show up on the OLED display. You can tare the scale by pressing the pushbutton. Once again, the readings on my ESP32 digital scale correspond to the readings on my kitchen scale.

Wrapping Up

In this tutorial, you learned how to interface a strain gauge load cell with the ESP32 using the HX711 amplifier. The output of the load cell is proportional to the force applied. So, you can calibrate it to be used in g, kg, ib, or any other unit that makes sense for your project. In summary, you learned how to calibrate the scale and how to get the weight of objects. You also learned how to create a simple digital scale with the ESP32 using an OLED display to show the measurements and a pushbutton to tare the scale.

Install ESP32 Filesystem Uploader in Arduino IDE

The ESP32 contains a Serial Peripheral Interface Flash File System (SPIFFS). SPIFFS is a lightweight filesystem created for microcontrollers with a flash chip, which is connected by SPI bus, like the ESP32 flash memory. In this article we're going to show how to easily upload files to the ESP32 filesystem using a plugin for Arduino IDE. Note: if you have an ESP8266 board, read: Install ESP8266 NodeMCU LittleFS Filesystem Uploader in Arduino IDE. At the moment, this is not compatible with Arduino 2.0. If you're using VS Code with the PlatformIO extension, read the following tutorial instead: ESP32 with VS Code and PlatformIO: Upload Files to Filesystem (SPIFFS) Table of Contents Introducing SPIFFS Installing the Arduino ESP32 Filesystem Uploader Windows Instructions MacOS X Instructions Uploading Files using the Filesystem Uploader Testing the Uploader

Introducing SPIFFS

SPIFFS lets you access the flash memory like you would do in a normal filesystem in your computer, but simpler and more limited. You can read, write, close, and delete files. At the time of writing this post, SPIFFS doesn't support directories, so everything is saved on a flat structure. Using SPIFFS with the ESP32 board is especially useful to: Create configuration files with settings; Save data permanently; Create files to save small amounts of data instead of using a microSD card; Save HTML and CSS files to build a web server; Save images, figures, and icons; And much more. With SPIFFS, you can write the HTML and CSS in separate files and save them on the ESP32 filesystem. Check the following tutorial to learn how to build a web server with files stored on the ESP32 file system: ESP32 Web Server using SPIFFS (SPI Flash File System)

Installing the Arduino ESP32 Filesystem Uploader

You can create, save and write files to the ESP32 filesystem by writing the code yourself on the Arduino IDE. This is not very useful, because you'd have to type the content of your files in the Arduino sketch. Fortunately, there is a plugin for the Arduino IDE that allows you to upload files directly to the ESP32 filesystem from a folder on your computer. This makes it really easy and simple to work with files. Let's install it. Note: at the time of writing this post, the ESP32 Filesystem Uploader plugin is not supported on Arduino 2.0. First, make sure you have the ESP32 add-on for the Arduino IDE. If you don't, follow the next tutorial: Windows, Mac, and Linux instructions Installing the ESP32 Board in Arduino IDE

Windows Instructions

Follow the next steps to install the filesystem uploader if you're using Windows: 1) Go to the releases page and click the ESP32FS-1.0.zip file to download. 2) Find your Sketchbook location. In your Arduino IDE, go to File > Preferences and check your Sketchbook location. In my case, it's in the following path: C:\Users\sarin\Documents\Arduino. 3) Go to the sketchbook location, and create a tools folder. 4) Unzip the downloaded .zip folder. Open it and copy the ESP32FS folder to the tools folder you created in the previous step. You should have a similar folder structure: <Sketchbook-location>/tools/ESP32FS/tool/esp32fs.jar 5) Finally, restart your Arduino IDE. To check if the plugin was successfully installed, open your Arduino IDE. Select your ESP32 board, go to Tools and check that you have the option ESP32 Sketch Data Upload.

MacOS X

Follow the next instructions if you're using MacOS X. 1) Go to the releases page and click the ESP32FS-1.0.zip file to download. 2) Unpack the files. 3) Create a folder called tools in /Documents/Arduino/. 4) Copy the unpacked ESP32FS folder to the tools directory. You should have a similar folder structure. ~Documents/Arduino/tools/ESP32FS/tool/esp32fs.jar 5) Finally, restart your Arduino IDE. To check if the plugin was successfully installed, open your Arduino IDE. Select your ESP32 board, go to Tools and check that you have the option ESP32 Sketch Data Upload.

Uploading Files using the Filesystem Uploader

To upload files to the ESP32 filesystem follow the next instructions. 1) Create an Arduino sketch and save it. For demonstration purposes, you can save an empty sketch. 2) Then, open the sketch folder. You can go to Sketch > Show Sketch Folder. The folder where your sketch is saved should open. Arduino IDE Show Sketch folder to create data folder 3) Inside that folder, create a new folder called data. ESP32 Arduino Sketch Example File Filesystem fs SPIFFS 4) Inside the data folder is where you should put the files you want to save into the ESP32 filesystem. As an example, create a .txt file with some text called test_example. ESP32 Notepad Test Example File Filesystem fs SPIFFS 5) Then, to upload the files, in the Arduino IDE, you just need to go to Tools > ESP32 Sketch Data Upload. ESP32 Sketch Data Upload Arduino IDE SPIFFS FS Filesystem The uploader will overwrite anything you had already saved in the filesystem. Note: in some ESP32 development boards you need to press the on-board BOOT button when you see the Connecting .____ message. The files were successfully uploaded to the ESP32 filesystem when you see the message SPIFFS Image Uploaded.

Testing the Uploader

Now, let's just check if the file was actually saved into the ESP32 filesystem. Simply upload the following code to your ESP32 board. /********* Rui Santos Complete project details at https://randomnerdtutorials.com *********/ #include "SPIFFS.h" void setup() { Serial.begin(115200); if(!SPIFFS.begin(true)){ Serial.println("An Error has occurred while mounting SPIFFS"); return; } File file = SPIFFS.open("/test_example.txt"); if(!file){ Serial.println("Failed to open file for reading"); return; } Serial.println("File Content:"); while(file.available()){ Serial.write(file.read()); } file.close(); } void loop() { } View raw code After uploading, open the Serial Monitor at a baud rate of 115200. Press the ESP32 ENABLE/RST button. It should print the content of your .txt file on the Serial Monitor. ESP32 SPIFFS FS Filesystem Example Arduino IDE Serial Monitor You've successfully uploaded files to the ESP32 filesystem using the plugin.

Wrapping Up

Using the filesystem uploader plugin is one of the easiest ways to upload files to the ESP32 filesystem. Check the following project to see how to build a web server using HTML and CSS files stored on the filesystem: ESP32 Web Server using SPIFFS (SPI Flash File System). Another way to save data permanently is using the ESP32 Preferences library. It is especially useful to save data as key:value pairs in the flash memory. Check the following tutorial: ESP32 Save Data Permanently using Preferences Library For more projects with ESP32, check the following resources: Learn ESP32 with Arduino IDE Build Web Servers with ESP32 and ESP8266 Firebase Web App with ESP32 and ESP8266 Free ESP32 Projects and Tutorials
SMART HOME with Raspberry Pi ESP32 and ESP8266 Node-RED InfluxDB eBook

SMART HOME with Raspberry Pi, ESP32, ESP8266 [eBook]

Learn how to build a home automation system and we'll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD Learn how to build a home automation system and we'll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD

with TDS Sensor (Water Quality Sensor)

In this guide, you'll learn how to use a TDS meter (Total Dissolved Solids) with the ESP32. A TDS meter indicates the total dissolved solids like salts, minerals, and metals, in a solution. This parameter can be used to give you an idea of water quality and compare water from different sources. One of the main applications of a TDS meter is aquarium water quality monitoring. We'll use the TDS meter from keystudio and show you a simple example to measure TDS in ppm units using Arduino IDE. Table of Contents In this tutorial, we'll cover the following topics Introducing the TDS Meter Interfacing the TDS Meter with the ESP32 Reading TDS with the ESP32 Arduino Code

Introducing the TDS Meter

A TDS meter measures the number of total dissolved solids like salts, minerals, and metals in the water. As the number of dissolved solids in the water increases, the conductivity of the water increases, and that allow us to calculate the total dissolved solids in ppm (mg/L). Although this is a good indicator to monitor the quality of the water, note that it does not measure contaminants in the water. Thus, you can't rely solely on this indicator to determine if the water is good for consumption or not. A TDS meter can be useful to monitor water quality in many applications like pools, aquariums, fish tanks, hydroponics, water purifiers, etc. In this tutorial, we'll use the TDS meter from keystudio that comes with an interface module and an electrode probe (see picture above). For more information about the TDS meter, we recommend taking a look at the official documentation.

Features and Specifications

This tutorial refers to the TDS Meter V1.0 from keystudio. Here are the sensor parameters: TDS Meter: Input Voltage: DC 3.3 ~ 5.5V Output Voltage: 0 ~ 2.3V Working Current: 3 ~ 6mA TDS Measurement Range: 0 ~ 1000ppm TDS Measurement Accuracy: 10% F.S. (25 ) Module Interface: XH2.54-3P Electrode Interface: XH2.54-2P TDS Probe: Number of Needle: 2 Total Length: 60cm Connection Interface: XH2.54-2P Color: White Waterproof Probe

Where to Buy TDS Sensor?

You can check the TDS sensor on Maker Advisor to find the best price: TDS Sensor You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Interfacing the TDS Meter with the ESP32

The TDS meter outputs an analog signal that can be measured using an ADC pin on the ESP32. You can check the ESP32 ADC pins here. Wire the sensor as in the following table:
TDS Sensor ESP32
GND GND
VCC 3.3V
Data GPIO 27 (or any other ESP32 ADC pin)

Reading TDS (water quality) with the ESP32 Code

As we mentioned previously, the sensor outputs an analog signal that can be converted to TDS in ppm. We're using the code provided by the sensor documentation with some modifications. To get more accurate results, you'll probably need to calibrate your sensor against a solution with a known TDS value. Also, take into account the non-linearity of the ESP32 ADC when it comes to low and high values. However, these adjustments might be not needed if you are not concerned about specific values but about a qualitative value of TDS. Upload the following code to your ESP32. // Original source code: https://wiki.keyestudio.com/KS0429_keyestudio_TDS_Meter_V1.0#Test_Code // Project details: https://RandomNerdTutorials.com/esp32-tds-water-quality-sensor/ #define TdsSensorPin 27 #define VREF 3.3 // analog reference voltage(Volt) of the ADC #define SCOUNT 30 // sum of sample point int analogBuffer[SCOUNT]; // store the analog value in the array, read from ADC int analogBufferTemp[SCOUNT]; int analogBufferIndex = 0; int copyIndex = 0; float averageVoltage = 0; float tdsValue = 0; float temperature = 25; // current temperature for compensation // median filtering algorithm int getMedianNum(int bArray[], int iFilterLen){ int bTab[iFilterLen]; for (byte i = 0; i<iFilterLen; i++) bTab[i] = bArray[i]; int i, j, bTemp; for (j = 0; j < iFilterLen - 1; j++) { for (i = 0; i < iFilterLen - j - 1; i++) { if (bTab[i] > bTab[i + 1]) { bTemp = bTab[i]; bTab[i] = bTab[i + 1]; bTab[i + 1] = bTemp; } } } if ((iFilterLen & 1) > 0){ bTemp = bTab[(iFilterLen - 1) / 2]; } else { bTemp = (bTab[iFilterLen / 2] + bTab[iFilterLen / 2 - 1]) / 2; } return bTemp; } void setup(){ Serial.begin(115200); pinMode(TdsSensorPin,INPUT); } void loop(){ static unsigned long analogSampleTimepoint = millis(); if(millis()-analogSampleTimepoint > 40U){ //every 40 milliseconds,read the analog value from the ADC analogSampleTimepoint = millis(); analogBuffer[analogBufferIndex] = analogRead(TdsSensorPin); //read the analog value and store into the buffer analogBufferIndex++; if(analogBufferIndex == SCOUNT){ analogBufferIndex = 0; } } static unsigned long printTimepoint = millis(); if(millis()-printTimepoint > 800U){ printTimepoint = millis(); for(copyIndex=0; copyIndex<SCOUNT; copyIndex++){ analogBufferTemp[copyIndex] = analogBuffer[copyIndex]; // read the analog value more stable by the median filtering algorithm, and convert to voltage value averageVoltage = getMedianNum(analogBufferTemp,SCOUNT) * (float)VREF / 4096.0; //temperature compensation formula: fFinalResult(25^C) = fFinalResult(current)/(1.0+0.02*(fTP-25.0)); float compensationCoefficient = 1.0+0.02*(temperature-25.0); //temperature compensation float compensationVoltage=averageVoltage/compensationCoefficient; //convert voltage value to tds value tdsValue=(133.42*compensationVoltage*compensationVoltage*compensationVoltage - 255.86*compensationVoltage*compensationVoltage + 857.39*compensationVoltage)*0.5; //Serial.print("voltage:"); //Serial.print(averageVoltage,2); //Serial.print("V "); Serial.print("TDS Value:"); Serial.print(tdsValue,0); Serial.println("ppm"); } } } View raw code

How the Code Works

Let's take a quick look at the code. You can also skip right away to the Demonstration section. The TdsSensorPin variable saves the GPIO where you want to get the readings. We chose GPIO27, but you can use any other ADC pin. #define TdsSensorPin 27 Then, insert the analog voltage reference for the ADC. For the ESP32 is 3.3V, for an Arduino, for example, it is 5V. #define VREF 3.3 // analog reference voltage(Volt) of the ADC Before getting a measurement value, we'll apply a median filtering algorithm to get a more stable value. The SCOUNT variable refers to the number of samples we'll filter before getting an actual value. #define SCOUNT 30 // sum of sample point Then, we need some arrays to store the readings as well as some index variables that will allow us to go through the arrays. int analogBuffer[SCOUNT]; // store the analog value in the array, read from ADC int analogBufferTemp[SCOUNT]; int analogBufferIndex = 0; int copyIndex = 0; Initialize the averageVoltage variable and tsdValue as float variables. float averageVoltage = 0; float tdsValue = 0; The temperature variable saves the current temperature value. The temperature influences the readings, so there is an algorithm that compensates for fluctuations in temperature. In this example, the reference temperature is 25oC, but you can change it depending on your environment. For more accurate results, you can add a temperature sensor and get the actual temperature at the time of reading the sensor. float temperature = 25; // current temperature for compensation The following function will be used to get a stable TDS value from an array of readings. // median filtering algorithm int getMedianNum(int bArray[], int iFilterLen){ int bTab[iFilterLen]; for (byte i = 0; i<iFilterLen; i++) bTab[i] = bArray[i]; int i, j, bTemp; for (j = 0; j < iFilterLen - 1; j++) { for (i = 0; i < iFilterLen - j - 1; i++) { if (bTab[i] > bTab[i + 1]) { bTemp = bTab[i]; bTab[i] = bTab[i + 1]; bTab[i + 1] = bTemp; } } } if ((iFilterLen & 1) > 0){ bTemp = bTab[(iFilterLen - 1) / 2]; } else { bTemp = (bTab[iFilterLen / 2] + bTab[iFilterLen / 2 - 1]) / 2; } return bTemp; } In the setup(), initialize the Serial Monitor at a baud rate of 115200. Serial.begin(115200); Set the TDS sensor pin as an input. pinMode(TdsSensorPin,INPUT); In the loop(), get new TDS readings every 40 milliseconds and save them in the buffer: static unsigned long analogSampleTimepoint = millis(); if(millis()-analogSampleTimepoint > 40U){ //every 40 milliseconds,read the analog value from the ADC analogSampleTimepoint = millis(); analogBuffer[analogBufferIndex] = analogRead(TdsSensorPin); //read the analog value and store into the buffer analogBufferIndex++; if(analogBufferIndex == SCOUNT){ analogBufferIndex = 0; } } Every 800 milliseconds, it gets the latest readings and gets the average voltage by using the filtering algorithm created before: static unsigned long printTimepoint = millis(); if(millis()-printTimepoint > 800U){ printTimepoint = millis(); for(copyIndex=0; copyIndex<SCOUNT; copyIndex++){ analogBufferTemp[copyIndex] = analogBuffer[copyIndex]; // read the analog value more stable by the median filtering algorithm, and convert to voltage value averageVoltage = getMedianNum(analogBufferTemp,SCOUNT) * (float)VREF / 4096.0; Then, it calculates a temperature compensation coefficient and calculates the TDS value taking that value into account: //temperature compensation formula: fFinalResult(25^C) = fFinalResult(current)/(1.0+0.02*(fTP-25.0)); float compensationCoefficient = 1.0+0.02*(temperature-25.0); //temperature compensation float compensationVoltage=averageVoltage/compensationCoefficient; //convert voltage value to tds value tdsValue=(133.42*compensationVoltage*compensationVoltage*compensationVoltage - 255.86*compensationVoltage*compensationVoltage + 857.39*compensationVoltage)*0.5; Finally, it prints the TDS value in ppm: Serial.print("TDS Value:"); Serial.print(tdsValue,0); Serial.println("ppm");

Demonstration

After copying the code to the Arduino IDE, upload the code to your board. Don't forget to select the right board in Tools > Board and the right COM port in Tools > Port. After uploading, open the Serial Monitor at a baud rate of 115200 and press the ESP32 RST button so that the code starts working. It will show a value of 0 if the probe is not submerged. Put the probe on a solution to check its TDS. You can try with tap water and add some salt to see if the values increase. I measured the TDS value for tap water in my house, and I got a value of around 100ppm, which is a good value for drinking water. I also tested tea, and the TDS value increased to about 230ppm, which seems a reasonable value. Finally, I also measured the TDS value of bottled water and I got a value of 0ppm. I'm not sure if this value is correct because the water is advertised as mineral water, so the minerals dissolved in the water should account for a TDS value. I think this value can be explained due to the non-linearity of the ESP32 ADC pins for small voltage values. Do you have one of these sensors? What values did you get for bottled water?

Wrapping Up

A TDS meter can measure the total dissolved solids in a solution. It can be used as an indicator of water quality and allows you to characterize the water. The meter returns the TDS value in ppm (parts per millionmg/L). The TDS value has many applications but it cannot be used by itself to determine if the water is drinkable or not. A great application of this type of sensor is an aquarium water quality monitor. You can use this sensor alongside a waterproof DS18B20 temperature sensor to monitor your fish tank, for example. Are you interested in an Aquarium Water Quality Monitor? I was thinking about creating a web app to monitor and control your aquarium temperature and water quality and additionally, also be able to control a pump via an output pin of the ESP32. What do you think?

K-Type Thermocouple with MAX6675 Amplifier

In this guide, you'll learn how to read temperature using a K-Type Thermocouple with the MAX6675 amplifier with the ESP32 board. A K-type thermocouple is a type of temperature sensor with a wide measurement range like 200 to 1260oC (326 to 2300oF). This tutorial covers how to interface the k-type thermocouple with your ESP32 board, install the required library and use a simple sketch to display the sensor readings in the Serial Monitor. Table of Contents In this tutorial, we'll cover the following topics: Introducing K-Type Thermocouple MAX 6675 Amplifier Interfacing K-Type Thermocouple with MAX 6675 Amplifier Installing MAX 6675 Arduino Library Code Get Temperature from K-Type Thermocouple with MAX 6675 Amplifier

What is a K-Type Thermocouple?

A thermocouple is a device that consists of two different electrical conductors that form an electrical junctionthermal junction. The change in temperature at the junction creates a slightly but measurable voltage at the reference junction that can be used to calculate the temperature. A thermocouple can be made of different metals. The metals used will affect the voltage range, cost, and sensitivity. There are standardized metal combinations that result in different thermocouple types: B, E, J, N, K, R, T, and S. Our tutorial is about the k-type thermocouple. A k-type thermocouple is made out of chrome and alumel conductors and has a general temperature range of 200 to 1260oC (328 to 2300oF).

MAX6675 Amplifier

To get the temperature from the thermocouple we need a thermocouple amplifier. The temperature output from the thermocouple amplifier depends on the voltage read on the reference junction. The voltage at the reference junction depends on the temperature difference between the reference junction and the thermal junction. So, we need to know the temperature at the reference junction. The MAX6675 thermocouple comes with a temperature sensor to measure temperature at the reference junction (cold-compensation reference) and amplifies the tiny voltage at the reference junction so that we can read it using our microcontrollers. The MAX6675 amplifier communicates with a microcontroller using SPI communication protocol and the data is output in a 12-bit resolution. Usually, you can get a pack with a k-type thermocouple and the MAX6675 amplifier. Here's a list of the MAX6675 most relevant features. For a more detailed description, please consult the MAX6675 datasheet. Direct digital conversion of type -K thermocouple output Cold-junction compensaiton Simple SPI-compatible serial interface Operating voltage range: 3.0 to 5.5V Operating temperature range: 20 to 85oC Resolves temperatures to 0.25oC, allows readings as high as 1024oC (1875oF).

Interfacing K-Type Thermocouple with MAX6675 Amplifier

As mentioned previously, the MAX 6675 communicates with a microcontroller using SPI communication protocol.
MAX6675 Microcontroller
SO MISO
CS CS
SCK CLK
VCC VCC (3.3V or 5V)
GND GND

Get Temperature from K-Type Thermocouple with MAX6675 Amplifier

In this section, you'll learn how to get temperature from your k-type thermocouple. We'll show you a simple example that reads the temperature and displays it on the Arduino IDE Serial Monitor.

Parts Required

To complete this tutorial, you need the following parts: K-type thermocouple with MAX6675 amplifier ESP32 (read Best ESP32 development boards) Jumper wires (female-to-female) You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Schematic ESP32 with K-type thermocouple and MAX6675 Amplifier

Wire the MAX6675 Amplifier to the ESP32 as shown in the following schematic diagram. You can also follow the next table.
MAX6675 ESP32
GND GND
VCC 3.3V
SCK GPIO 5
CS GPIO 23
SO GPIO 19

Installing MAX6675 Arduino Library

There are different libraries to get temperature from a K-type thermocouple using the MAX6675 amplifier. We'll use the max6675 library from Adafruit. Follow the next steps to install the library in your Arduino IDE: Open your Arduino IDE and go to Sketch > Include Library > Manage Libraries. The Library Manager should open. Search for max6675 in the search box and install the library from Adafruit.

Code Get Temperature from K-Type Thermocouple with MAX 6675 Amplifier

Getting temperature from the K-Type thermocouple with the ESP32 is very simple. The library provides an example that gets temperature and displays the results on the Arduino IDE Serial monitor. The code was adapted from the example provided by the library to make it compatible with the ESP32. // this example is public domain. enjoy! https://learn.adafruit.com/thermocouple/ #include "max6675.h" int thermoDO = 19; int thermoCS = 23; int thermoCLK = 5; MAX6675 thermocouple(thermoCLK, thermoCS, thermoDO); void setup() { Serial.begin(9600); Serial.println("MAX6675 test"); // wait for MAX chip to stabilize delay(500); } void loop() { // basic readout test, just print the current temp Serial.print("C = "); Serial.println(thermocouple.readCelsius()); Serial.print("F = "); Serial.println(thermocouple.readFahrenheit()); // For the MAX6675 to update, you must delay AT LEAST 250ms between reads! delay(1000); } View raw code

How the Code Works

First, include the max6675.h library. #include "max6675.h" Define the pins that are interfacing with the MAX6675 thermocouple amplifier. int thermoDO = 19; int thermoCS = 23; int thermoCLK = 5; Create a MAX6675 object called thermocouple on the pins we've defined previously. MAX6675 thermocouple(thermoCLK, thermoCS, thermoDO); In the setup(), initialize the Serial Monitor at a baud rate of 9600. Serial.begin(9600); In the loop(), we read the temperature and display it on the Serial Monitor. The library provides a method to read the temperature in Celsius and a method to read the temperature in Fahrenheit degrees. thermocouple.readCelsius(): returns temperature in Celsius degrees. thermocouple.readFahrenheit(): returns temperature in Fahrenheit degrees. The following lines read the temperature and display it on the Serial Monitor. Serial.print("C = "); Serial.println(thermocouple.readCelsius()); Serial.print("F = "); Serial.println(thermocouple.readFahrenheit()); As you can see, it's very simple to get temperature readings using the K-type thermocouple with the MAX6675 amplifier.

Demonstration

Upload the code to your ESP32 board. Don't forget the select the board you're using in Tools > Board and select the COM port your board is connected to in Tools > Port. After uploading the code to the ESP32, open the Serial Monitor at a baud rate of 9600. Press the ESP32 on-board RST button. New temperature readings are displayed on the Serial Monitor every second.

Wrapping Up

In this tutorial, you learned how to read temperature using the k-type thermocouple with the MAX6675 amplifier. Thermocouples have a wide temperature measurement range and allow you to read very high temperaturesas high as 1024oC (1875oF) when using k-type thermocouple with MAX6675. We have tutorials for other popular sensors with the ESP32 board that you may like: ESP32 with DS18B20: Temperature Sensor ESP32 with BME680: Gas, Pressure, Humidity, and Temperature Sensor ESP32 with BME280: Temperature, Humidity, and Pressure Sensor ESP32 DHT11/DHT22: Temperature, and Humidity Sensor ESP32 with BMP388: Altimeter Sensor ESP32 HC-SR04: Ultrasonic Distance Sensor ESP32 PIR: Motion Sensor ESP32 BMP180: Pressure Sensor ESP32 with BH1750 Ambient Light Sensor

with BH1750 Ambient Light Sensor

The BH1750 is a 16-bit ambient light sensor. In this guide, you'll learn how to use the BH1750 ambient light sensor with the ESP32 board using Arduino IDE. The sensor communicates with a microcontroller using I2C communication protocol. You'll learn how to wire the sensor to the ESP32 board, install the required libraries and use a simple sketch to display the sensor readings in the Serial Monitor. This tutorial covers the following topics: Introducing the BH1750 Ambient Light Sensor BH1750 Pinout BH1750 I2C Interface Example: BH1750: Read Ambient Light with ESP32

Introducing BH1750 Ambient Light Sensor

The BH1750 is a 16-bit ambient light sensor that communicates via I2C protocol. It outputs luminosity measurements in lux (SI-derived unit of illuminance). It can measure a minimum of 1 lux and a maximum of 65535 lux. The sensor may come in different breakout board formats. See pictures below. Both images represent a BH1750 sensor.

BH1750 Features

Here's a list of the BH1750 sensor features. For more information consult the BH1750 sensor datasheet. I2C bus Interface Spectral responsibility is approximately human eye response Illuminance to digital converter Range: 1 65535 lux Low current by power down function 50Hz / 60Hz Light noise reject-function It is possible to select 2 different I2 C slave addresses Small measurement variation (+/- 20%) The influence of infrared is very small Supports continuous measurement mode Supports one-time measurement mode

Measurement Modes

The sensor supports two different measurement modes: continuous measurement mode, and one-time measurement mode. Each mode supports three different resolution modes.
Low Resolution Mode 4 lux precision 16 ms measurement time
High Resolution Mode 1 lux precision 120 ms measurement time
High Resolution Mode 2 0.5 lux precision 120 ms measurement time
In continuous measurement mode, the sensor continuously measures ambient light values. In one-time measurement mode, the sensor measures the ambient light value once, and then it goes to power down mode.

Applications

The BH1750 is an ambient light sensor so it can be used in a wide variety of projects. For example: to detect if it is day or night; to adjust or turn on/off LED's brightness accordingly to ambient light; to adjust LCDs and screen's brightness; to detect if an LED is lit;

BH1750 Pinout

Here's the BH1750 Pinout:
VCC Powers the sensor (3.3V or 5V)
GND Common GND
SCL SCL pin for I2C communication
SDA (Data) SDA pin for I2C communication
ADD* Selects address
The ADD pin is used to set the I2C sensor address. If the voltage on that pin is less than 0.7VCC (pin is left floating or connected to GND), the I2C address is 0x23. But, if the voltage is higher than 0.7xVCC (pin is connected to VCC), the address is 0x5C. In summary: ADD pin floating or connected to GND address: 0x23 ADD pin connected to VCC address: 0x5C

BH1750 I2C Interface

The BH1750 ambient light sensor supports I2C interface. You can connect the BH1750 sensor to the ESP32 using the default's I2C pins:
BH1750 ESP32
SCL GPIO 22
SDA GPIO 21
GPIO 22 and GPIO 21 are the ESP32 default I2C pins. You can use other pins as long as you set them properly on code.

BH1750: Read Ambient Light with ESP32

Now that you are more familiar with the BH1750 sensor, let's test it. In this section, we'll build a simple project that reads the ambient light and displays it in the Arduino IDE Serial Monitor.

Parts Required

To complete this tutorial you need the following parts: BH1750 ambient light sensor ESP32 (read Best ESP32 development boards) Breadboard (optional) Jumper wires (optional) You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Schematic ESP32 with BH1750

Wire the BH1750 sensor to the ESP32 I2C pins. You can follow the next schematic diagram. You can also follow the next table:
BH1750 ESP32
VCC 3.3V
GND GND
SCL GPIO 22
SDA (Data) GPIO 21
ADD* Don't connect
By not connecting the ADD pin, we're selecting 0x23 I2C address. Connect it to 3.3V to select 0x5C address instead.

Preparing Arduino IDE

We'll program the ESP32 board using Arduino IDE. So, make sure you have the ESP32 add-on installed. Follow the next tutorial: Installing the ESP32 Board in Arduino IDE (Windows, Mac OS X, Linux)

Installing the BH1750 Library

There are several libraries to read from the BH1750 sensor. We'll use the BH1750 library by Christopher Laws. It is compatible with the ESP32, ESP8266, and Arduino. Open your Arduino IDE and go to Sketch > Include Library > Manage Libraries. The Library Manager should open. Search for BH1750 on the search box and install the BH1750 library by Christopher Laws.

Code Reading BH1750 Ambient Light Sensor

Copy the following code to your Arduino IDE. This code simply reads ambient light in lux and displays the values on the Serial Monitor. It is the example code from the library called BH1750test (you can access it in File > Examples > BH1750 > BH1750test /* Example of BH1750 library usage. This example initialises the BH1750 object using the default high resolution continuous mode and then makes a light level reading every second. */ #include <Wire.h> #include <BH1750.h> BH1750 lightMeter; void setup(){ Serial.begin(9600); // Initialize the I2C bus (BH1750 library doesn't do this automatically) Wire.begin(); // On esp8266 you can select SCL and SDA pins using Wire.begin(D4, D3); // For Wemos / Lolin D1 Mini Pro and the Ambient Light shield use Wire.begin(D2, D1); lightMeter.begin(); Serial.println(F("BH1750 Test begin")); } void loop() { float lux = lightMeter.readLightLevel(); Serial.print("Light: "); Serial.print(lux); Serial.println(" lx"); delay(1000); } View raw code The library also provides other examples worth exploring.

How the Code Works

We start by including the required libraries. The Wire.h library to use I2C communication protocol and the BH1750.h library to read from the sensor. #include <Wire.h> #include <BH1750.h> Then, we create a BH1750 object called lightMeter. BH1750 lightMeter; In the setup(), initialize the Serial Monitor at a baud rate of 9600. Serial.begin(9600); Initialize I2C communication protocol. It will start an I2C communication on the microcontroller's default I2C pins. If you want to use different I2C pins, pass them to the begin() method like this Wire.begin(SDA, SCL). Wire.begin(); Initialize the sensor using the begin() method on the BH1750 object (lightMeter). lightMeter.begin(); In the loop(), we create a variable called lux, that saves the luminance values. To get the value, you simply call the readLightLevel() function on the BH1750 object (lightMeter). float lux = lightMeter.readLightLevel(); Finally, display the measurement on the Serial Monitor. Serial.print("Light: "); Serial.print(lux); Serial.println(" lx"); You get and print a new reading every second. delay(1000);

Demonstration

Now, you can upload the code to your board. First, connect your board to your computer. Then, go to Tools > Board and select the ESP32 board you're using. Go to Tools > Port and select the COM port your board is connected to. Finally, click on the upload button. After successfully uploading the code, open the Serial Monitor at a baud rate of 9600 and press the ESP32 on-board RST button. New luminance readings should be printed in the Serial Monitor.

Other Useful Functions

The library we're using with the BH1750 sensor provides other examples that illustrate other useful functions and features. You can check all BH1750 library examples here.

Setting Measurement Mode

By default, the library uses the continuous high resolution measurement mode, but you can change it by passing the desired measurement mode to the begin() method when initializing the sensor. For example: lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE) Here's a list of all available modes: BH1750_CONTINUOUS_LOW_RES_MODE BH1750_CONTINUOUS_HIGH_RES_MODE (default) BH1750_CONTINUOUS_HIGH_RES_MODE_2 BH1750_ONE_TIME_LOW_RES_MODE BH1750_ONE_TIME_HIGH_RES_MODE BH1750_ONE_TIME_HIGH_RES_MODE_2 See the properties of each mode in this previous section.

Wrapping Up

In this tutorial, you've learned how to use the BH1750 ambient light sensor with the ESP32. The sensor is very easy to use. It uses I2C communication protocol, which makes wiring simple, and the library provides methods to easily get the readings.

Firebase Data Logging Web App (Gauges, Charts, and Table)

In this project, you'll create a Firebase Web App that displays all the sensor readings saved on the Firebase Realtime Database. We'll create a web interface with gauges, charts, and a table to display all your data records. We'll also add a button that allows you to delete all data from the database and checkboxes to customize the user interface. This web application will be protected with authentication (using email and password) and all the data is restricted to the user using database rules. This project is Part 2 of the following tutorial (there is a version for ESP32 and a version for ESP8266): ESP32 Data Logging to Firebase Realtime Database ESP8266 Data Logging to Firebase Realtime Database You must follow one of those tutorials first, before proceeding Here's a summary of the web app features: login with email and password displays time of the last update cards to display the last sensor readings gauges to display the last sensor readings charts that display data history with timestamps select how many readings to display on charts checkboxes to enable/disable the different display options table that displays all readings saved on the database button to delete database data

Project Overview

In this tutorial (Part 2), you'll create a web app to display the sensor readings logged with timestamps on the Firebase Realtime Database (read this previous tutorial ESP32 version / ESP8266 version). The following video shows the web app project we'll buildprogramming the ESP32/ESP8266 and setting up the Firebase Project was done in Part 1 (ESP32 Part 1; ESP8266 Part 1). Firebase hosts your web app over a global CDN using Firebase Hosting and provides an SSL certificate. You can access your web app from anywhere using the Firebase-generated domain name. When you first access the web app, you need to authenticate with an authorized email address and password. You already set up that user and the authentication method in Part 1. After authentication, you can access a web app page that shows the sensor readings. The sensor readings are displayed in cards, gauges, charts and table. You can select how many readings you want to show on the charts and you can also choose how you can view your data. There is a button to show/hide all readings saved on the database on a table with timestamps. There's also a Delete button that allows you to delete all data from the database. All the data is restricted using database rules.

Prerequisites

Before start creating the Firebase Web App, you need to check the following prerequisites:

Creating a Firebase Project

You should have followed one of the next tutorials first: ESP32 Data Logging to Firebase Realtime Database ESP8266 Data Logging to Firebase Realtime Database The ESP32/ESP8266 must be running the code provided in that tutorial. The realtime database and authentication must be set up also as shown in the tutorial.

Install Required Software

Before getting started you need to install the required software to create the Firebase Web App. Here's a list of the software you need to install (click on the links for instructions): Visual Studio Code Node.JS LTS version Install Firebase Tools

1) Add an App to Your Firebase Project

1) Go to your Firebase project Console and add an app to your project by clicking on the +Add app button. 2) Select the web app icon. 3) Give your app a name. Then, check the box next to Also set up Firebase Hosting for this App. Click Register app. 4) Then, copy the firebaseConfig object and save it because you'll need it later. After this, you can also access the firebaseConfig object if you go to your Project settings in your Firebase console. 5) Click Next on the proceeding steps, and finally on Continue to console.

2) Setting Up a Firebase Web App Project (VS Code)

Follow the next steps to create a Firebase Web App Project using VS Code.

1) Creating a Project Folder

1) Create a folder on your computer where you want to save your Firebase projectfor example, Firebase-Project on the Desktop. 2) Open VS Code. Go to File > Open Folder and select the folder you've just created. 3) Go to Terminal > New Terminal. A new Terminal window should open on your project path.

2) Firebase Login

4) On the previous Terminal window, type the following: firebase login 5) You'll be asked to collect CLI usage and error reporting information. Enter n and press Enter to deny. Note: If you are already logged in, it will show a message saying: Already logged in as [email protected]. 6) After this, it will pop up a new window on your browser to login into your firebase account. 7) Allow Firebase CLI to access your google account. 8) After this, Firebase CLI login should be successful. You can close the browser window.

3) Initializing Web App Firebase Project

9) After successfully login in, run the following command to start a Firebase project directory in the current folder. firebase init 10) You'll be asked if you want to initialize a Firebase project in the current directory. Enter Y and hit Enter. 11) Then, use up and down arrows and the Space key to select the options. Select the following options: Realtime Database: Configure security rules file for Realtime Database and (optionally) provision default instance. Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys The selected options will show up with a green asterisk. Then, hit Enter. 12) Select the option Use an existing projectit should be highlighted in bluethen, hit Enter. 13) After that, select the Firebase project for this directoryit should be the project created in this previous tutorial. In my case, it is called esp-firebase-demo. Then hit Enter. 14) Press Enter on the following question to select the default database security rules file: What file should be used for Realtime Database Security Rules? 15) Then, select the hosting options as shown below: What do you want to use as your public directory? Hit Enter to select public. Configure as a single-page app (rewrite urls to /index.html)? No Set up automatic builds and deploys with GitHub? No 16) The Firebase project should now be initialized successfully. Notice that VS code created some essential files under your project folder. The index.html file contains some HTML text to build a web page. For now, leave the default HTML text. The idea is to replace that with your own HTML text to build a custom web page for your needs. We'll do that later in this tutorial. 17) To check if everything went as expected, run the following command on the VS Code Terminal window. firebase deploy You should get a Deploy complete! message and an URL to the Project Console and the Hosting URL. 18) Copy the hosting URL and paste it into a web browser window. You should see the following web page. You can access that web page from anywhere in the world. The web page you've seen previously is built with the HTML file placed in the public folder of your Firebase project. By changing the content of that file, you can create your own web app. That's what we're going to do in the next section.

3) Creating Firebase Web App

Now that you've created a Firebase project app successfully on VS Code, follow the next steps to customize the app to display the sensor readings on a login-protected web page.

index.html

Copy the following to your index.html file (it is inside the public folder). <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>ESP Datalogging Firebase App</title> <!-- include Firebase SDK --> <script src="https://www.gstatic.com/firebasejs/8.8.1/firebase-app.js"></script> <!-- include only the Firebase features as you need --> <script src="https://www.gstatic.com/firebasejs/8.8.1/firebase-auth.js"></script> <script src="https://www.gstatic.com/firebasejs/8.8.1/firebase-database.js"></script> <script> // Replace with your app config object const firebaseConfig = { apiKey: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", authDomain: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", databaseURL: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", projectId: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", storageBucket: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", messagingSenderId: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", appId: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION" }; // Initialize firebase firebase.initializeApp(firebaseConfig); // Make auth and database references const auth = firebase.auth(); const db = firebase.database(); </script> <!-- include highchartsjs to build the charts--> <script src="https://code.highcharts.com/highcharts.js"></script> <!-- include to use jquery--> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> <!--include icons from fontawesome--> <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous"> <!-- include Gauges Javascript library--> <script src="https://cdn.rawgit.com/Mikhus/canvas-gauges/gh-pages/download/2.1.7/all/gauge.min.js"></script> <!--reference for favicon--> <link rel="icon" type="image/png" href="favicon.png"> <!--reference a stylesheet--> <link rel="stylesheet" type="text/css" href="style.css"> </head> <body> <!--TOP BAR--> <div> <h1>Sensor Readings App <i></i></h2> </div> <!--AUTHENTICATION BAR (USER DETAILS/LOGOUT BUTTON)--> <div style="display: none;"> <p><span>User logged in</span> <span>USEREMAIL</span> <a href="/">(logout)</a> </p> </div> <!--LOGIN FORM--> <form style="display: none;"> <div> <label for="input-email"><b>Email</b></label> <input type="text" placeholder="Enter Username" required> <label for="input-password"><b>Password</b></label> <input type="password" placeholder="Enter Password" required> <button type="submit">Login</button> <p style="color:red;"></p> </div> </form> <!--CONTENT (SENSOR READINGS)--> <div style="display: none;"> <!--LAST UPDATE--> <p><span class ="date-time">Last update: <span></span></span></p> <p> Cards: <input type="checkbox" name="cards-checkbox" checked> Gauges: <input type="checkbox" name="gauges-checkbox" checked> Charts: <input type="checkbox" name="charts-checkbox" unchecked> </p> <div> <div> <!--TEMPERATURE--> <div> <p><i style="color:#059e8a;"></i> TEMPERATURE</p> <p><span><span></span> °C</span></p> </div> <!--HUMIDITY--> <div> <p><i style="color:#00add6;"></i> HUMIDITY</p> <p><span><span></span> %</span></p> </div> <!--PRESSURE--> <div> <p><i style="color:#e1e437;"></i> PRESSURE</p> <p><span><span></span> hPa</span></p> </div> </div> </div> <!--GAUGES--> <div id ="gauges-div"> <div> <!--TEMPERATURE--> <div> <canvas></canvas> </div> <!--HUMIDITY--> <div> <canvas></canvas> </div> </div> </div> <!--CHARTS--> <div style="display:none"> <!--SET NUMBER OF READINGS INPUT FIELD--> <div> <p> Number of readings: <input type="number"></p> </div> <!--TEMPERATURE-CHART--> <div> <div> <p><i style="color:#059e8a;"></i> TEMPERATURE CHART</p> <div></div> </div> </div> <!--HUMIDITY-CHART--> <div> <div> <p><i style="color:#00add6;"></i> HUMIDITY CHART</p> <div></div> </div> </div> <!--PRESSURE-CHART--> <div> <div> <p><i style="color:#e1e437;"></i> PRESSURE CHART</p> <div></div> </div> </div> </div> <!--BUTTONS TO HANDLE DATA--> <p> <!--View data button--> <button>View all data</button> <!--Hide data button--> <button style= "display:none;">Hide data</button> <!--Delete data button--> <button>Delete data</button> </p> <!--Modal to delete data--> <div sytle="display:none"> <span onclick = "document.getElementById('delete-modal').style.display='none'" title="Close Modal"></span> <form id= "delete-data-form" action="/"> <div> <h1>Delete Data</h2> <p>Are you sure you want to delete all data from database?</p> <div> <button type="button" onclick="document.getElementById('delete-modal').style.display='none'">Cancel</button> <button type="submit" onclick="document.getElementById('delete-modal').style.display='none'">Delete</button> </div> </div> </form> </div> <!--TABLE WITH ALL DATA--> <div class ="cards"> <div style= "display:none;"> <table> <tr> <th>Timestamp</th> <th>Temp (oC)</th> <th>Hum (%)</th> <th>Pres (hPa)</th> </tr> <tbody> </tbody> </table> <p><button style= "display:none;">More results...</button></p> </div> </div> </div> <!--INCLUDE JS FILES--> <script src="scripts/auth.js"></script> <script src="scripts/charts-definition.js"></script> <script src="scripts/gauges-definition.js"></script> <script src="scripts/index.js"></script> </body> </html> View raw code Important: you need to modify the code with your own firebaseConfig objectthe one you've got in this step. const firebaseConfig = { apiKey: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", authDomain: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", databaseURL: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", projectId: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", storageBucket: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", messagingSenderId: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", appId: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION" };

style.css

Inside the public folder create a file called style.css. To create the file, select the public folder, and then click on the +file icon at the top of the File Explorer. Call it style.css. Then, copy the following to the style.css file html { font-family: Verdana, Geneva, Tahoma, sans-serif; display: inline-block; text-align: center; } body { margin: 0; width: 100%; } .topnav { overflow: hidden; background-color: #049faa; color: white; font-size: 1rem; padding: 5px; } #authentication-bar{ background-color:mintcream; padding-top: 10px; padding-bottom: 10px; } #user-details{ color: cadetblue; } .content { padding: 20px; } .card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); padding: 5%; } .cards { max-width: 800px; margin: 0 auto; margin-bottom: 10px; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(200px, 2fr)); } .reading { color: #193036; } .date-time{ font-size: 0.8rem; color: #1282A2; } button { background-color: #049faa; color: white; padding: 14px 20px; margin: 8px 0; border: none; cursor: pointer; border-radius: 4px; } button:hover { opacity: 0.8; } .deletebtn{ background-color: #c52c2c; } .form-elements-container{ padding: 16px; width: 250px; margin: 0 auto; } input[type=text], input[type=password] { width: 100%; padding: 12px 20px; margin: 8px 0; display: inline-block; border: 1px solid #ccc; box-sizing: border-box; } table { width: 100%; text-align: center; font-size: 0.8rem; } tr, td { padding: 0.25rem; } tr:nth-child(even) { background-color: #f2f2f2 } tr:hover { background-color: #ddd; } th { position: sticky; top: 0; background-color: #50b8b4; color: white; } /* The Modal (background) */ .modal { display: none; /* Hidden by default */ position: fixed; /* Stay in place */ z-index: 1; /* Sit on top */ left: 0; top: 0; width: 100%; /* Full width */ height: 100%; /* Full height */ overflow: auto; /* Enable scroll if needed */ background-color: #474e5d; padding-top: 50px; } /* Modal Content/Box */ .modal-content { background-color: #fefefe; margin: 5% auto 15% auto; /* 5% from the top, 15% from the bottom and centered */ border: 1px solid #888; width: 80%; /* Could be more or less, depending on screen size */ } /* Style the horizontal ruler */ hr { border: 1px solid #f1f1f1; margin-bottom: 25px; } /* The Modal Close Button (x) */ .close { position: absolute; right: 35px; top: 15px; font-size: 40px; font-weight: bold; color: #f1f1f1; } .close:hover, .close:focus { color: #f44336; cursor: pointer; } /* Clear floats */ .clearfix::after { content: ""; clear: both; display: table; } /* Change styles for cancel button and delete button on extra small screens */ @media screen and (max-width: 300px) { .cancelbtn, .deletebtn { width: 100%; } } View raw code The CSS file includes some simple styles to make our webpage look better. We won't discuss how CSS works in this tutorial.

JavaScript Files

We'll create four JavaScript files (auth.js, index.js, charts-definition.js, and gauges-definition.js) inside a scripts folder inside the public folder. Select the public folder, then click on the +folder icon to create a new folder. Call scripts to that new folder. Then, select the scripts folder and click on the +file icon. Create a file called auth.js. Then, repeat the previous steps to create the index.js, charts-definition.js, and gauges-definition.js files. The following image shows what your web app project folder structure should look like.

auth.js

Copy the following to the auth.js file you created previously. document.addEventListener("DOMContentLoaded", function(){ // listen for auth status changes auth.onAuthStateChanged(user => { if (user) { console.log("user logged in"); console.log(user); setupUI(user); var uid = user.uid; console.log(uid); } else { console.log("user logged out"); setupUI(); } }); // login const loginForm = document.querySelector('#login-form'); loginForm.addEventListener('submit', (e) => { e.preventDefault(); // get user info const email = loginForm['input-email'].value; const password = loginForm['input-password'].value; // log the user in auth.signInWithEmailAndPassword(email, password).then((cred) => { // close the login modal & reset form loginForm.reset(); console.log(email); }) .catch((error) =>{ const errorCode = error.code; const errorMessage = error.message; document.getElementById("error-message").innerHTML = errorMessage; console.log(errorMessage); }); }); // logout const logout = document.querySelector('#logout-link'); logout.addEventListener('click', (e) => { e.preventDefault(); auth.signOut(); }); }); View raw code Then, save the file. This file takes care of everything related to the login and logout of the user.

index.js

The index.js file handles the UIit shows the right content depending on the user authentication status. When the user is logged in, this file gets new readings from the database whenever there's a change and displays them in the right places. Copy the following to the index.js file. // convert epochtime to JavaScripte Date object function epochToJsDate(epochTime){ return new Date(epochTime*1000); } // convert time to human-readable format YYYY/MM/DD HH:MM:SS function epochToDateTime(epochTime){ var epochDate = new Date(epochToJsDate(epochTime)); var dateTime = epochDate.getFullYear() + "/" + ("00" + (epochDate.getMonth() + 1)).slice(-2) + "/" + ("00" + epochDate.getDate()).slice(-2) + " " + ("00" + epochDate.getHours()).slice(-2) + ":" + ("00" + epochDate.getMinutes()).slice(-2) + ":" + ("00" + epochDate.getSeconds()).slice(-2); return dateTime; } // function to plot values on charts function plotValues(chart, timestamp, value){ var x = epochToJsDate(timestamp).getTime(); var y = Number (value); if(chart.series[0].data.length > 40) { chart.series[0].addPoint([x, y], true, true, true); } else { chart.series[0].addPoint([x, y], true, false, true); } } // DOM elements const loginElement = document.querySelector('#login-form'); const contentElement = document.querySelector("#content-sign-in"); const userDetailsElement = document.querySelector('#user-details'); const authBarElement = document.querySelector('#authentication-bar'); const deleteButtonElement = document.getElementById('delete-button'); const deleteModalElement = document.getElementById('delete-modal'); const deleteDataFormElement = document.querySelector('#delete-data-form'); const viewDataButtonElement = document.getElementById('view-data-button'); const hideDataButtonElement = document.getElementById('hide-data-button'); const tableContainerElement = document.querySelector('#table-container'); const chartsRangeInputElement = document.getElementById('charts-range'); const loadDataButtonElement = document.getElementById('load-data'); const cardsCheckboxElement = document.querySelector('input[name=cards-checkbox]'); const gaugesCheckboxElement = document.querySelector('input[name=gauges-checkbox]'); const chartsCheckboxElement = document.querySelector('input[name=charts-checkbox]'); // DOM elements for sensor readings const cardsReadingsElement = document.querySelector("#cards-div"); const gaugesReadingsElement = document.querySelector("#gauges-div"); const chartsDivElement = document.querySelector('#charts-div'); const tempElement = document.getElementById("temp"); const humElement = document.getElementById("hum"); const presElement = document.getElementById("pres"); const updateElement = document.getElementById("lastUpdate") // MANAGE LOGIN/LOGOUT UI const setupUI = (user) => { if (user) { //toggle UI elements loginElement.style.display = 'none'; contentElement.style.display = 'block'; authBarElement.style.display ='block'; userDetailsElement.style.display ='block'; userDetailsElement.innerHTML = user.email; // get user UID to get data from database var uid = user.uid; console.log(uid); // Database paths (with user UID) var dbPath = 'UsersData/' + uid.toString() + '/readings'; var chartPath = 'UsersData/' + uid.toString() + '/charts/range'; // Database references var dbRef = firebase.database().ref(dbPath); var chartRef = firebase.database().ref(chartPath); // CHARTS // Number of readings to plot on charts var chartRange = 0; // Get number of readings to plot saved on database (runs when the page first loads and whenever there's a change in the database) chartRef.on('value', snapshot =>{ chartRange = Number(snapshot.val()); console.log(chartRange); // Delete all data from charts to update with new values when a new range is selected chartT.destroy(); chartH.destroy(); chartP.destroy(); // Render new charts to display new range of data chartT = createTemperatureChart(); chartH = createHumidityChart(); chartP = createPressureChart(); // Update the charts with the new range // Get the latest readings and plot them on charts (the number of plotted readings corresponds to the chartRange value) dbRef.orderByKey().limitToLast(chartRange).on('child_added', snapshot =>{ var jsonData = snapshot.toJSON(); // example: {temperature: 25.02, humidity: 50.20, pressure: 1008.48, timestamp:1641317355} // Save values on variables var temperature = jsonData.temperature; var humidity = jsonData.humidity; var pressure = jsonData.pressure; var timestamp = jsonData.timestamp; // Plot the values on the charts plotValues(chartT, timestamp, temperature); plotValues(chartH, timestamp, humidity); plotValues(chartP, timestamp, pressure); }); }); // Update database with new range (input field) chartsRangeInputElement.onchange = () =>{ chartRef.set(chartsRangeInputElement.value); }; //CHECKBOXES // Checbox (cards for sensor readings) cardsCheckboxElement.addEventListener('change', (e) =>{ if (cardsCheckboxElement.checked) { cardsReadingsElement.style.display = 'block'; } else{ cardsReadingsElement.style.display = 'none'; } }); // Checbox (gauges for sensor readings) gaugesCheckboxElement.addEventListener('change', (e) =>{ if (gaugesCheckboxElement.checked) { gaugesReadingsElement.style.display = 'block'; } else{ gaugesReadingsElement.style.display = 'none'; } }); // Checbox (charta for sensor readings) chartsCheckboxElement.addEventListener('change', (e) =>{ if (chartsCheckboxElement.checked) { chartsDivElement.style.display = 'block'; } else{ chartsDivElement.style.display = 'none'; } }); // CARDS // Get the latest readings and display on cards dbRef.orderByKey().limitToLast(1).on('child_added', snapshot =>{ var jsonData = snapshot.toJSON(); // example: {temperature: 25.02, humidity: 50.20, pressure: 1008.48, timestamp:1641317355} var temperature = jsonData.temperature; var humidity = jsonData.humidity; var pressure = jsonData.pressure; var timestamp = jsonData.timestamp; // Update DOM elements tempElement.innerHTML = temperature; humElement.innerHTML = humidity; presElement.innerHTML = pressure; updateElement.innerHTML = epochToDateTime(timestamp); }); // GAUGES // Get the latest readings and display on gauges dbRef.orderByKey().limitToLast(1).on('child_added', snapshot =>{ var jsonData = snapshot.toJSON(); // example: {temperature: 25.02, humidity: 50.20, pressure: 1008.48, timestamp:1641317355} var temperature = jsonData.temperature; var humidity = jsonData.humidity; var pressure = jsonData.pressure; var timestamp = jsonData.timestamp; // Update DOM elements var gaugeT = createTemperatureGauge(); var gaugeH = createHumidityGauge(); gaugeT.draw(); gaugeH.draw(); gaugeT.value = temperature; gaugeH.value = humidity; updateElement.innerHTML = epochToDateTime(timestamp); }); // DELETE DATA // Add event listener to open modal when click on "Delete Data" button deleteButtonElement.addEventListener('click', e =>{ console.log("Remove data"); e.preventDefault; deleteModalElement.style.display="block"; }); // Add event listener when delete form is submited deleteDataFormElement.addEventListener('submit', (e) => { // delete data (readings) dbRef.remove(); }); // TABLE var lastReadingTimestamp; //saves last timestamp displayed on the table // Function that creates the table with the first 100 readings function createTable(){ // append all data to the table var firstRun = true; dbRef.orderByKey().limitToLast(100).on('child_added', function(snapshot) { if (snapshot.exists()) { var jsonData = snapshot.toJSON(); console.log(jsonData); var temperature = jsonData.temperature; var humidity = jsonData.humidity; var pressure = jsonData.pressure; var timestamp = jsonData.timestamp; var content = ''; content += '<tr>'; content += '<td>' + epochToDateTime(timestamp) + '</td>'; content += '<td>' + temperature + '</td>'; content += '<td>' + humidity + '</td>'; content += '<td>' + pressure + '</td>'; content += '</tr>'; $('#tbody').prepend(content); // Save lastReadingTimestamp --> corresponds to the first timestamp on the returned snapshot data if (firstRun){ lastReadingTimestamp = timestamp; firstRun=false; console.log(lastReadingTimestamp); } } }); }; // append readings to table (after pressing More results... button) function appendToTable(){ var dataList = []; // saves list of readings returned by the snapshot (oldest-->newest) var reversedList = []; // the same as previous, but reversed (newest--> oldest) console.log("APEND"); dbRef.orderByKey().limitToLast(100).endAt(lastReadingTimestamp).once('value', function(snapshot) { // convert the snapshot to JSON if (snapshot.exists()) { snapshot.forEach(element => { var jsonData = element.toJSON(); dataList.push(jsonData); // create a list with all data }); lastReadingTimestamp = dataList[0].timestamp; //oldest timestamp corresponds to the first on the list (oldest --> newest) reversedList = dataList.reverse(); // reverse the order of the list (newest data --> oldest data) var firstTime = true; // loop through all elements of the list and append to table (newest elements first) reversedList.forEach(element =>{ if (firstTime){ // ignore first reading (it's already on the table from the previous query) firstTime = false; } else{ var temperature = element.temperature; var humidity = element.humidity; var pressure = element.pressure; var timestamp = element.timestamp; var content = ''; content += '<tr>'; content += '<td>' + epochToDateTime(timestamp) + '</td>'; content += '<td>' + temperature + '</td>'; content += '<td>' + humidity + '</td>'; content += '<td>' + pressure + '</td>'; content += '</tr>'; $('#tbody').append(content); } }); } }); } viewDataButtonElement.addEventListener('click', (e) =>{ // Toggle DOM elements tableContainerElement.style.display = 'block'; viewDataButtonElement.style.display ='none'; hideDataButtonElement.style.display ='inline-block'; loadDataButtonElement.style.display = 'inline-block' createTable(); }); loadDataButtonElement.addEventListener('click', (e) => { appendToTable(); }); hideDataButtonElement.addEventListener('click', (e) => { tableContainerElement.style.display = 'none'; viewDataButtonElement.style.display = 'inline-block'; hideDataButtonElement.style.display = 'none'; }); // IF USER IS LOGGED OUT } else{ // toggle UI elements loginElement.style.display = 'block'; authBarElement.style.display ='none'; userDetailsElement.style.display ='none'; contentElement.style.display = 'none'; } } View raw code

charts-definition.js

Copy the following to the charts-definition.js file. This file creates the different charts using the highcharts javascript library. // Create the charts when the web page loads window.addEventListener('load', onload); function onload(event){ chartT = createTemperatureChart(); chartH = createHumidityChart(); chartP = createPressureChart(); } // Create Temperature Chart function createTemperatureChart() { var chart = new Highcharts.Chart({ chart:{ renderTo:'chart-temperature', type: 'spline' }, series: [ { name: 'BME280' } ], title: { text: undefined }, plotOptions: { line: { animation: false, dataLabels: { enabled: true } } }, xAxis: { type: 'datetime', dateTimeLabelFormats: { second: '%H:%M:%S' } }, yAxis: { title: { text: 'Temperature Celsius Degrees' } }, credits: { enabled: false } }); return chart; } // Create Humidity Chart function createHumidityChart(){ var chart = new Highcharts.Chart({ chart:{ renderTo:'chart-humidity', type: 'spline' }, series: [{ name: 'BME280' }], title: { text: undefined }, plotOptions: { line: { animation: false, dataLabels: { enabled: true } }, series: { color: '#50b8b4' } }, xAxis: { type: 'datetime', dateTimeLabelFormats: { second: '%H:%M:%S' } }, yAxis: { title: { text: 'Humidity (%)' } }, credits: { enabled: false } }); return chart; } // Create Pressure Chart function createPressureChart() { var chart = new Highcharts.Chart({ chart:{ renderTo:'chart-pressure', type: 'spline' }, series: [{ name: 'BME280' }], title: { text: undefined }, plotOptions: { line: { animation: false, dataLabels: { enabled: true } }, series: { color: '#A62639' } }, xAxis: { type: 'datetime', dateTimeLabelFormats: { second: '%H:%M:%S' } }, yAxis: { title: { text: 'Pressure (hPa)' } }, credits: { enabled: false } }); return chart; } View raw code

gauges-definition.js

In our web app, we'll display a gauge for the temperature and another for the humidity. The gauges-definition.js file contains functions to create the gauges. // Create Temperature Gauge function createTemperatureGauge() { var gauge = new LinearGauge({ renderTo: 'gauge-temperature', width: 120, height: 400, units: "Temperature C", minValue: 0, startAngle: 90, ticksAngle: 180, maxValue: 40, colorValueBoxRect: "#049faa", colorValueBoxRectEnd: "#049faa", colorValueBoxBackground: "#f1fbfc", valueDec: 2, valueInt: 2, majorTicks: [ "0", "5", "10", "15", "20", "25", "30", "35", "40" ], minorTicks: 4, strokeTicks: true, highlights: [ { "from": 30, "to": 40, "color": "rgba(200, 50, 50, .75)" } ], colorPlate: "#fff", colorBarProgress: "#CC2936", colorBarProgressEnd: "#049faa", borderShadowWidth: 0, borders: false, needleType: "arrow", needleWidth: 2, needleCircleSize: 7, needleCircleOuter: true, needleCircleInner: false, animationDuration: 1500, animationRule: "linear", barWidth: 10, }); return gauge; } // Create Humidity Gauge function createHumidityGauge(){ var gauge = new RadialGauge({ renderTo: 'gauge-humidity', width: 300, height: 300, units: "Humidity (%)", minValue: 0, maxValue: 100, colorValueBoxRect: "#049faa", colorValueBoxRectEnd: "#049faa", colorValueBoxBackground: "#f1fbfc", valueInt: 2, majorTicks: [ "0", "20", "40", "60", "80", "100" ], minorTicks: 4, strokeTicks: true, highlights: [ { "from": 80, "to": 100, "color": "#03C0C1" } ], colorPlate: "#fff", borderShadowWidth: 0, borders: false, needleType: "line", colorNeedle: "#007F80", colorNeedleEnd: "#007F80", needleWidth: 2, needleCircleSize: 3, colorNeedleCircleOuter: "#007F80", needleCircleOuter: true, needleCircleInner: false, animationDuration: 1500, animationRule: "linear" }); return gauge; } View raw code

Favicon File

To display a favicon in your web app, you need to move the picture you want to use as favicon to the public folder. The picture should be called favicon.png. You can simply drag the favicon file from your computer into the public folder in VS Code. We're using the following icon as a favicon for our web app: favicon.png

Deploy your App

After saving the HTML, CSS, and JavaScript files, deploy your app on VS Code by running the following command on the Terminal window. firebase deploy The Terminal should display something as follows: Firebase offers a free hosting service to serve your assets and web apps. Then, you can access your web app from anywhere. You can use the Hosting URL provided to access your web app from anywhere.

Demonstration

Congratulations! You successfully deployed your app. It is now hosted on a global CDN using Firebase hosting. You can access your web app from anywhere on the Hosting URL provided. In my case, it is https://esp-firebase-demo.web.app. The web app is responsive, and you can access it using your smartphone, computer, or tablet. When you first access the web app, you'll see a form to insert the email username and password. Insert the email and password of the authorized user you added in the Firebase Authentication methods. If the form doesn't show up at first, refresh the web page. After that, you can access the web page with the readings. The readings are displayed in cards, gauges, charts, and a table. You can also select which interfaces you want to see by checking/unchecking the checkboxes. You can also check the readings displayed on charts. You can select the charts range, but keep in mind that selecting more than 30 readings will take some time. Finally, if you want to see all the readings. You can open the readings table. At the end of the table, there's a button to load more readings until all readings are displayed. There is also a button to delete all data if you want to remove all readings from the database. Here's a video showing how the web app works.

Wrapping Up

In this tutorial, you created a Firebase Web App with login/logout authentication that displays sensor readings in many different ways. The sensor readings are saved on the realtime database. The database is protected using database rules (that you've already set up in a previous tutorial). You can apply what you learned here to display any other type of data, and you can change the files in the public folder to add different functionalities and features to your project. We didn't explain how the javascript files work because the project is quite long. However, if there is enough interest in this subject, we can split this application into smaller projects so that you understand how to handle data using queries and how to display it in different ways. Let us know what you think in the comments below.

Data Logging to Firebase Realtime Database

In this guide, you'll learn how to log data with the ESP32 to the Firebase Realtime Database with timestamps (data logging) so that you have a record of your data history. As an example, we'll log temperature, humidity, and pressure from a BME280 sensor and we'll get timestamps from an NTP server. Then, you can access the data using the Firebase console, or build a web app to display the results (check this tutorial). Part 2: ESP32/ESP8266: Firebase Data Logging Web App (Gauges, Charts, and Table) Other Firebase Tutorials with the ESP32/ESP8266 that you might be interested in: ESP32: Getting Started with Firebase (Realtime Database) ESP8266 NodeMCU: Getting Started with Firebase (Realtime Database) ESP32 with Firebase Creating a Web App ESP8266 NodeMCU with Firebase Creating a Web App ESP32/ESP8266 Firebase Authentication (Email and Password) ESP32/ESP8266 Firebase: Send BME280 Sensor Readings to the Realtime Database ESP32/ESP8266: Firebase Web App to Display Sensor Readings (with Authentication)

What is Firebase?

Firebase is Google's mobile application development platform that helps you build, improve, and grow your app. It has many services used to manage data from any android, IOS, or web application like authentication, realtime database, hosting, etc.

Project Overview

The following diagram shows a high-level overview of the project we'll build.
    The ESP32 authenticates as a user with email and password (that user must be set on the Firebase authentication methods); After authentication, the ESP gets the user UID; The database is protected with security rules. The user can only access the database nodes under the node with its user UID. After getting the user UID, the ESP can publish data to the database; The ESP32 gets temperatrure, humidity and pressure from the BME280 sensor. It gets epoch time right after gettings the readings (timestamp). The ESP32 sends temperature, humidity, pressure and timestamp to the database. New readings are added to the database periodically. You'll have a record of all readings on the Firebase realtime database.
These are the main steps to complete this project:
    Create Firebase Project Set Authentication Methods Get Project API Key Set up Realtime Database Set up Database Security Rules ESP32 Datalogging (Firebase Realtime Database)
You can continue with the Firebase project from this previous tutorial or create a new project. If you use the Firebase project of that previous tutorial, you can skip to section 4) Set up Realtime Database because the authentication methods are already set up.

Preparing Arduino IDE

For this tutorial, we'll program the ESP32 board using the Arduino core. So, make sure you have the ESP32 add-on installed in your Arduino IDE: Installing the ESP32 Board in Arduino IDE (Windows, Mac OS X, Linux) If you want to program the ESP boards using VS Code with the PlatformIO extension, follow the following tutorial instead: Getting Started with VS Code and PlatformIO IDE for ESP32 and ESP8266

1) Create Firebase Proje3t

1) Go to Firebase and sign in using a Google Account. 2) Click Get Started and then Add project to create a new project. 3) Give a name to your project, for example, ESP Firebase Demo. 4) Disable the option Enable Google Analytics for this project as it is not needed and click Create project. 5) It will take a few seconds to set up your project. Then, click Continue when it's ready. 6) You'll be redirected to your Project console page.

2) Set Authentication Methods

To allow authentication with email and password, first, you need to set authentication methods for your app. Most apps need to know the identity of a user. In other words, it takes care of logging in and identifying the users (in this case, the ESP32). Knowing a user's identity allows an app to securely save user data in the cloud and provide the same personalized experience across all of the user's devices. To learn more about the authentication methods, you can read the documentation. 1) On the left sidebar, click on Authentication and then on Get started. 2) Select the Option Email/Password. 3) Enable that authentication method and click Save. 4) The authentication with email and password should now be enabled. 5) Now, you need to add a user. On the Authentication tab, select the Users tab at the top. Then, click on Add User. 6) Add an email address for the authorized user. It can be your google account email or any other email. You can also create an email for this specific project. Add a password that will allow you to sign in to your app and access the database. Don't forget to save the password in a safe place because you'll need it later. When you're done, click Add user. 7) A new user was successfully created and added to the Users table. Notice that Firebase creates a unique UID for each registered user. The user UID allows us to identify the user and keep track of the user to provide or deny access to the project or the database. There's also a column that registers the date of the last sign-in. At the moment, it is empty because we haven't signed in with that user yet.

3) Get Project API Key

To interface with your Firebase project using the ESP32 board, you need to get your project API key. Follow the next steps to get your project API key. 1) On the left sidebar, click on Project Settings. 2) Copy the Web API Key to a safe place because you'll need it later.

4) Set up Realtime Database

Now, let's create a realtime database and set up database rules for our project. 1) On the left sidebar, click on Realtime Database and then click on Create Database. 2) Select your database location. It should be the closest to your location. 3) Set up security rules for your database. You can select Start in test mode. We'll change the database rules in just a moment. 4) Your database is now created. You need to copy and save the database URLhighlighted in the following imagebecause you'll need it later in your ESP32 code.

5) Set up Database Security Rules

Now, let's set up the database rules. On the Realtime Database tab, select the Rules tab at the top. Then, click on Edit rules, copy the following rules and then click Publish. // These rules grant access to a node matching the authenticated // user's ID from the Firebase auth token { "rules": { "UsersData": { "$uid": { ".read": "$uid === auth.uid", ".write": "$uid === auth.uid" } } } } These rules grant access to a node matching the authenticated user's UID. This grants that each authenticated user can only access its own data. This means the user can only access the nodes that are under a node with its corresponding user UID. If there are other data published on the database, not under a node with the users' UID, that user can't access that data. For example, imagine our user UID is RjO3taAzMMXBB2Xmir2LQ. With our security rules, it can read and write data to the database under the node UsersData/RjO3taAzMMXBB2Xmir2LQ. You'll better understand how this works when you start working with the ESP32.

6) ESP32 Datalogging (Firebase Realtime Database)

In this section, we'll program the ESP32 board to do the following tasks:
    Authenticate as a user with email and password (the user you set up in this section); Get BME280 readings: temperature, humidity, and pressure; Get epoch time (timestamp) from an NTP server; Send sensor readings and timestamp to the realtime database as an authorized user.

Parts Required

For this project, you need the following parts*: ESP32 board (read best ESP32 development boards); BME280 or any other sensor you're familiar with; Breadboard; Jumper wires. * you can also test the project with random values instead of sensor readings, or you can use any other sensor you're familiar with. You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Schematic Diagram

In this tutorial, we'll send BME280 sensor readings to the Firebase Realtime Database. So, you need to wire the BME280 sensor to your board. We're going to use I2C communication with the BME280 sensor module. For that, wire the sensor to the default ESP32 SCL (GPIO 22) and SDA (GPIO 21) pins, as shown in the following schematic diagram. Not familiar with the BME280 with the ESP32? Read this tutorial: ESP32 with BME280 Sensor using Arduino IDE (Pressure, Temperature, Humidity).

Installing Libraries

For this project, you need to install the following libraries: Firebase ESP Client Library Adafruit BME280 Library Adafruit Unified Sensor Library

Installing Libraries VS Code

Follow the next instructions if you're using VS Code with the PlatformIO extension.
Install the Firebase-ESP-Client Library
There is a library with lots of examples to use Firebase with the ESP32: the Firebase-ESP-Client library. This library is compatible with both the ESP32 and ESP8266 boards. Click on the PIO Home icon and select the Libraries tab. Search for Firebase ESP Client. Select the Firebase Arduino Client Library for ESP8266 and ESP32. Then, click Add to Project and select the project you're working on. Install the BME280 Library In the Libraries tab, search for BME280. Select the Adafruit BME280 library. Then, click Add to Project and select the project you're working on. Also, change the monitor speed to 115200 by adding the following line to the platformio.ini file of your project: monitor_speed = 115200

Installation Arduino IDE

Follow this section if you're using Arduino IDE. You need to install the following libraries: Firebase ESP Client Library Adafruit BME280 Library Adafruit Unified Sensor Library Go to Sketch > Include Library > Manage Libraries, search for the libraries' names and install the libraries. For the Firebase Client library, select the Firebase Arduino Client Library for ESP8266 and ESP32. Now, you're all set to start programming the ESP32 board to interact with the database.

DataloggingFirebase Realtime Database Code

Copy the following code to your Arduino IDE or to the main.cpp file if you're using VS Code. You need to insert your network credentials, project API key, database URL, and the authorized user email and password. /* Rui Santos Complete project details at our blog: https://RandomNerdTutorials.com/esp32-data-logging-firebase-realtime-database/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ #include <Arduino.h> #include <WiFi.h> #include <Firebase_ESP_Client.h> #include <Wire.h> #include <Adafruit_Sensor.h> #include <Adafruit_BME280.h> #include "time.h" // Provide the token generation process info. #include "addons/TokenHelper.h" // Provide the RTDB payload printing info and other helper functions. #include "addons/RTDBHelper.h" // Insert your network credentials #define WIFI_SSID "REPLACE_WITH_YOUR_SSID" #define WIFI_PASSWORD "REPLACE_WITH_YOUR_PASSWORD" // Insert Firebase project API Key #define API_KEY "REPLACE_WITH_YOUR_PROJECT_API_KEY" // Insert Authorized Email and Corresponding Password #define USER_EMAIL "REPLACE_WITH_THE_USER_EMAIL" #define USER_PASSWORD "REPLACE_WITH_THE_USER_PASSWORD" // Insert RTDB URLefine the RTDB URL #define DATABASE_URL "REPLACE_WITH_YOUR_DATABASE_URL" // Define Firebase objects FirebaseData fbdo; FirebaseAuth auth; FirebaseConfig config; // Variable to save USER UID String uid; // Database main path (to be updated in setup with the user UID) String databasePath; // Database child nodes String tempPath = "/temperature"; String humPath = "/humidity"; String presPath = "/pressure"; String timePath = "/timestamp"; // Parent Node (to be updated in every loop) String parentPath; int timestamp; FirebaseJson json; const char* ntpServer = "pool.ntp.org"; // BME280 sensor Adafruit_BME280 bme; // I2C float temperature; float humidity; float pressure; // Timer variables (send new readings every three minutes) unsigned long sendDataPrevMillis = 0; unsigned long timerDelay = 180000; // Initialize BME280 void initBME(){ if (!bme.begin(0x76)) { Serial.println("Could not find a valid BME280 sensor, check wiring!"); while (1); } } // Initialize WiFi void initWiFi() { WiFi.begin(WIFI_SSID, WIFI_PASSWORD); Serial.print("Connecting to WiFi .."); while (WiFi.status() != WL_CONNECTED) { Serial.print('.'); delay(1000); } Serial.println(WiFi.localIP()); Serial.println(); } // Function that gets current epoch time unsigned long getTime() { time_t now; struct tm timeinfo; if (!getLocalTime(&timeinfo)) { //Serial.println("Failed to obtain time"); return(0); } time(&now); return now; } void setup(){ Serial.begin(115200); // Initialize BME280 sensor initBME(); initWiFi(); configTime(0, 0, ntpServer); // Assign the api key (required) config.api_key = API_KEY; // Assign the user sign in credentials auth.user.email = USER_EMAIL; auth.user.password = USER_PASSWORD; // Assign the RTDB URL (required) config.database_url = DATABASE_URL; Firebase.reconnectWiFi(true); fbdo.setResponseSize(4096); // Assign the callback function for the long running token generation task */ config.token_status_callback = tokenStatusCallback; //see addons/TokenHelper.h // Assign the maximum retry of token generation config.max_token_generation_retry = 5; // Initialize the library with the Firebase authen and config Firebase.begin(&config, &auth); // Getting the user UID might take a few seconds Serial.println("Getting User UID"); while ((auth.token.uid) == "") { Serial.print('.'); delay(1000); } // Print user UID uid = auth.token.uid.c_str(); Serial.print("User UID: "); Serial.println(uid); // Update database path databasePath = "/UsersData/" + uid + "/readings"; } void loop(){ // Send new readings to database if (Firebase.ready() && (millis() - sendDataPrevMillis > timerDelay || sendDataPrevMillis == 0)){ sendDataPrevMillis = millis(); //Get current timestamp timestamp = getTime(); Serial.print ("time: "); Serial.println (timestamp); parentPath= databasePath + "/" + String(timestamp); json.set(tempPath.c_str(), String(bme.readTemperature())); json.set(humPath.c_str(), String(bme.readHumidity())); json.set(presPath.c_str(), String(bme.readPressure()/100.0F)); json.set(timePath, String(timestamp)); Serial.printf("Set json... %s\n", Firebase.RTDB.setJSON(&fbdo, parentPath.c_str(), &json) ? "ok" : fbdo.errorReason().c_str()); } } View raw code

How the Code Works

Continue reading to learn how the code works or skip to the demonstration section.

Include Libraries

First, include the required libraries. The WiFi.h library to connect the ESP32 to the internet, the Firebase_ESP_Client.h library to interface the boards with Firebase, the Wire, Adafruit_Sensor, and Adafruit_BME280 to interface with the BME280 sensor, and the time library to get the time. #include <Arduino.h> #include <WiFi.h> #include <Firebase_ESP_Client.h> #include <Wire.h> #include <Adafruit_Sensor.h> #include <Adafruit_BME280.h> #include "time.h" You also need to include the following for the Firebase library to work. // Provide the token generation process info. #include "addons/TokenHelper.h" // Provide the RTDB payload printing info and other helper functions. #include "addons/RTDBHelper.h"

Network Credentials

Include your network credentials in the following lines so that your boards can connect to the internet using your local network. // Insert your network credentials #define WIFI_SSID "REPLACE_WITH_YOUR_SSID" #define WIFI_PASSWORD "REPLACE_WITH_YOUR_PASSWORD"

Firebase Project API Key, Firebase User, and Database URL

Insert your Firebase project API keythe one you've gotten in this section. #define API_KEY "REPLACE_WITH_YOUR_PROJECT_API_KEY" Insert the authorized email and the corresponding passwordthese are the details of the user you've added in this section. // Insert Authorized Email and Corresponding Password #define USER_EMAIL "REPLACE_WITH_THE_USER_EMAIL" #define USER_PASSWORD "REPLACE_WITH_THE_USER_PASSWORD" Insert your database URL in the following line: // Insert RTDB URLefine the RTDB URL #define DATABASE_URL "REPLACE_WITH_YOUR_DATABASE_URL"

Firebase Objects and Other Variables

The following line defines a FirebaseData object. FirebaseData fbdo; The next line defines a FirebaseAuth object needed for authentication. FirebaseAuth auth; Finally, the following line defines a FirebaseConfig object required for configuration data. FirebaseConfig config; The uid variable will be used to save the user's UID. We can get the user's UID after the authentication. String uid; The databasePath variable saves the database main path, which will be updated later with the user UID. String databasePath; The following variables save the database child nodes for the temperature, humidity, pressure, and timestamp. String tempPath = "/temperature"; String humPath = "/humidity"; String presPath = "/pressure"; String timePath = "/timestamp"; The parentPath is the parent node that will be updated in every loop with the current timestamp. // Parent Node (to be updated in every loop) String parentPath; To better understand how we'll organize our data, here's a diagram. It might seem redundant to save the timestamp twice (in the parent node and in the child node), however, having all the data at the same level of the hierarchy will make things simpler in the future, if we want to build a web app to display the data. The timestamp variable will be used to save time (epoch time format). int timestamp; To learn more about getting epoch time with the ESP32 board, you can check the following tutorial: Get Epoch/Unix Time with the ESP32 (Arduino IDE) We'll send all the readings and corresponding timestamp to the realtime database at the same time by creating a JSON object that contains the values of those variables. The ESP Firebase Client library has its own JSON methods. We'll use them to send data in JSON format to the database. We start by creating a variable of type FirebaseJson called json. FirebaseJson json; The ESP_Firebase_Client library provides some examples showing how to use FirebaseJson and how to send data in JSON format to the database: ESP_Firebase_Client library FirebaseJson examples. We'll request the time from pool.ntp.org, which is a cluster of time servers that anyone can use to request the time. const char* ntpServer = "pool.ntp.org"; Then, create an Adafruit_BME280 object called bme. This automatically creates a sensor object on the ESP32 default I2C pins. Adafruit_BME280 bme; // I2C The following variables will hold the temperature, humidity, and pressure readings from the sensor. float temperature; float humidity; float pressure;

Delay Time

The sendDataPrevMillis and timerDelay variables are used to check the delay time between each send. In this example, we're setting the delay time to 3 minutes (18000 milliseconds). Once you test this project and check that everything is working as expected, we recommend increasing the delay. // Timer variables (send new readings every three minutes) unsigned long sendDataPrevMillis = 0; unsigned long timerDelay = 180000;

initBME()

The initBME() function initializes the BME280 library using the bme object created previously. Then, you should call this library in the setup(). void initBME(){ if (!bme.begin(0x76)) { Serial.println("Could not find a valid BME280 sensor, check wiring!"); while (1); } }

initWiFi()

The initWiFi() function connects your ESP to the internet using the network credentials provided. You must call this function later in the setup() to initialize WiFi. // Initialize WiFi void initWiFi() { WiFi.begin(WIFI_SSID, WIFI_PASSWORD); Serial.print("Connecting to WiFi .."); while (WiFi.status() != WL_CONNECTED) { Serial.print('.'); delay(1000); } Serial.println(WiFi.localIP()); Serial.println(); }

getTime()

The getTime() function returns the current epoch time. // Function that gets current epoch time unsigned long getTime() { time_t now; struct tm timeinfo; if (!getLocalTime(&timeinfo)) { //Serial.println("Failed to obtain time"); return(0); } time(&now); return now; }

setup()

In setup(), initialize the Serial Monitor for debugging purposes at a baud rate of 115200. Serial.begin(115200); Call the initBME() function to initialize the BME280 sensor. initBME(); Call the initWiFi() function to initialize WiFi. initWiFi(); Configure the time: configTime(0, 0, ntpServer); Assign the API key to the Firebase configuration. config.api_key = API_KEY; The following lines assign the email and password to the Firebase authentication object. auth.user.email = USER_EMAIL; auth.user.password = USER_PASSWORD; Assign the database URL to the Firebase configuration object. config.database_url = DATABASE_URL; Add the following to the configuration object. // Assign the callback function for the long running token generation task config.token_status_callback = tokenStatusCallback; //see addons/TokenHelper.h // Assign the maximum retry of token generation config.max_token_generation_retry = 5; Initialize the Firebase library (authenticate) with the configuration and authentication settings we defined earlier. // Initialize the library with the Firebase authen and config Firebase.begin(&config, &auth); After initializing the library, we can get the user UID by calling auth.token.uid. Getting the user's UID might take some time, so we add a while loop that waits until we get it. // Getting the user UID might take a few seconds Serial.println("Getting User UID"); while ((auth.token.uid) == "") { Serial.print('.'); delay(1000); } Finally, we save the user's UID in the uid variable and print it in the Serial Monitor. uid = auth.token.uid.c_str(); Serial.print("User UID: "); Serial.print(uid); After getting the user UID, we can update the database path to include the user UID. // Update database path databasePath = "/UsersData/" + uid + "/readings";

loop()

In the loop(), check if it is time to send new readings: if (Firebase.ready() && (millis() - sendDataPrevMillis > timerDelay || sendDataPrevMillis == 0)){ sendDataPrevMillis = millis(); If it is, get the current time and save it in the timestamp variable. //Get current timestamp timestamp = getTime(); Serial.print ("time: "); Serial.println (timestamp); Update the parentPath variable to include the timestamp. parentPath= databasePath + "/" + String(timestamp); Then, add data to the json object by using the set() method and passing as first argument the child node destination (key) and as second argument the value: json.set(tempPath.c_str(), String(bme.readTemperature())); json.set(humPath.c_str(), String(bme.readHumidity())); json.set(presPath.c_str(), String(bme.readPressure()/100.0F)); json.set(timePath, String(timestamp)); Finally, call Firebase.RTDB.setJSON(&fbdo, parentPath.c_str(), &json) to append the data to the parent path. We can call that instruction inside a Serial.printf() command to print the results in the Serial Monitor at the same time the command runs. Serial.printf("Set json... %s\n", Firebase.RTDB.setJSON(&fbdo, parentPath.c_str(), &json) ? "ok" : fbdo.errorReason().c_str());

Demonstration

Upload the previous code to your ESP32 board. Don't forget to insert your network credentials, project API key, database URL, user email, and the corresponding password. After uploading the code, press the board RST button so that it starts running the code. It should authenticate to Firebase, get the user UID, and immediately send new readings to the database. Open the Serial Monitor at a baud rate of 115200 and check that everything is working as expected. Aditionally, go to the Realtime Database on your Firebase project interface and check that new readings are saved. Notice that it saves the data under a node with the own user UIDthis is a way to restrict access to the database. Wait some time until you get some readings on the database. Expand the nodes to check the data.

Wrapping Up

In this tutorial, you learned how to log your sensor readings with timestamps to the Firebase Realtime Database using the ESP32. This was just a simple example for you to understand how it works. You can use other methods provided by the ESP_Firebase_Client library to log your data, and you can organize your database in different ways. We organized the database in a way that is convenient for another project that we'll publish soon. In PART 2, we'll create a Firebase Web App to display all saved data in a table and the latest readings on charts. Part 2: ESP32/ESP8266: Firebase Data Logging Web App (Gauges, Charts, and Table) We hope you've found this tutorial useful. If you like Firebase projects, please take a look at our new eBook. We're sure you'll like it: Firebase Web App with ESP32 and ESP8266

CAM Save Picture in Firebase Storage

In this guide, you'll learn how to take and upload a picture to Firebase Storage using the ESP32-CAM. You'll create a Firebase project with Storage that allows you to store your files. Then, you can access your Firebase console to visualize the pictures or create a web app to display them (we'll do this in a future tutorial). The ESP32-CAM will be programmed using Arduino IDE. Updated 19 September 2023. Note: this project is compatible with any ESP32 Camera Board with the OV2640 camera. You just need to make sure you use the right pinout for the board you're using.

What is Firebase?

Firebase is Google's mobile application development platform that helps you build, improve, and grow your app. It has many services used to manage data from any Android, IOS, or web application like authentication, realtime database, hosting, storage, etc.

Project Overview

This simple tutorial exemplifies how to take and send photos taken with the ESP32-CAM to Firebase Storage. The ESP32-CAM takes a picture and sends it to Firebase every time it resets (press the RST button). The idea is that you add some sort of trigger that might be useful for your projects, like a PIR motion sensor or a pushbutton, for example. When the ESP32 first runs, it takes a new picture and saves it in the filesystem (LittleFS); The ESP32-CAM connects to Firebase as a user with email and password; The ESP32-CAM sends the picture to Firebase Storage; After that, you can go to your Firebase console to view the pictures; Later, you can build a web app that you can access from anywhere to display the ESP32-CAM pictures (we'll create this in a future tutorial).

Contents

Here's a summary of the steps you need to follow to create this project.
    Create a Firebase Project Set Authentication Methods Create Storage Bucket Get Project API Key ESP32-CAM Send Pictures to Firebase Storage

1) Create a Firebase Project

1) Go to Firebase and sign in using a Google Account. 2) Click Get Started and then Add project to create a new project. 3) Give a name to your project, for example: ESP Firebase Demo. 4) Disable the option Enable Google Analytics for this project as it is not needed and click Create project. 5) It will take a few seconds to set up your project. Then, click Continue when it's ready. 6) You'll be redirected to your Project console page.

2) Set Authentication Methods

To allow authentication with email and password, first, you need to set authentication methods for your app. Most apps need to know the identity of a user. In other words, it takes care of logging in and identifying the users (in this case, the ESP32-CAM). Knowing a user's identity allows an app to securely save user data in the cloud and provide the same personalized experience across all of the user's devices. To learn more about the authentication methods, you can read the documentation. 1) On the left sidebar, click on Authentication and then on Get started. 2) Select the Option Email/Password. 3) Enable that authentication method and click Save. 4) The authentication with email and password should now be enabled. 5) Now, you need to add a user. Still on the Authentication tab, select the Users tab at the top. Then, click on Add User. 6) Add an email address for the authorized user. It can be your google account email or any other email. You can also create an email for this specific project. Add a password that will allow you to sign in to your app and access the storage files. Don't forget to save the password in a safe place because you'll need it later. When you're done, click Add user. 7) A new user was successfully created and added to the Users table. Notice that Firebase creates a unique UID for each registered user. The user UID allows us to identify the user and keep track of the user to provide or deny access to the project or the database. There's also a column that registers the date of the last sign-in. At the moment, it is empty because we haven't signed in with that user yet.

3) Create Storage Bucket

1) On the left sidebar, click on Storage and then on Get started. 2) Select Start in test modeclick Next. We'll change the storage rules later on. 3) Select your storage locationit should be the closest to your country. 4) Wait a few seconds while it creates the storage bucket. 5) The storage bucket is now set up. Copy the storage bucket ID because you'll need it later (copy only the section highlighted with a red rectangle as shown below).

Storage Rules

We'll change the storage rules so that only authenticated users can upload files to the storage bucket. Select the Rules tab. Change your database rules. Use the following rules: rules_version = '2'; service firebase.storage { match /b/{bucket}/o { match /{allPaths=**} { allow read, write: if request.auth !=null; } } } When you're done, click Publish.

4) Get Project API Key

To interface with your Firebase project using the ESP32-CAM, you need to get your project API key. Follow the next steps to get your project API key. 1) On the left sidebar, click on Project Settings. 2) Copy the Web API Key to a safe place because you'll need it later.

5) ESP32-CAM Send Pictures to Firebase Storage

Before proceeding with the tutorial, make sure you check the following prerequisites.

Installing the ESP32 add-on

We'll program the ESP32-CAM board using Arduino IDE. So you need the Arduino IDE installed as well as the ESP32 add-on. Follow the next tutorial to install it, if you haven't already. Installing the ESP32 Board in Arduino IDE (Mac OS X and Linux instructions)

Installing ESP Firebase Client Library

The Firebase-ESP-Client library provides several examples to interface with Firebase services. It provides an example that shows how to send files to Firebase Storage. Our code we'll be based on that example. So, you need to make sure you have that library installed.

Installation VS Code + PlatformIO

If you're using VS Code with the PlatformIO extension, click on the PIO Home icon and select the Libraries tab. Search for Firebase ESP Client . Select the Firebase Arduino Client Library for ESP8266 and ESP32. Then, click Add to Project and select the project you're working on. Also, change the monitor speed to 115200 by adding the following line to the platformio.ini file of your project: monitor_speed = 115200

Installation Arduino IDE

If you're using Arduino IDE, follow the next steps to install the library.
    Go to Sketch > Include Library > Manage Libraries Search for Firebase ESP Client and install the Firebase Arduino Client Library for ESP8266 and ESP32 by Mobizt.
Now, you're all set to start programming the ESP32-CAM board to send pictures to Firebase Storage.

ESP32-CAM Send Pictures to Firebase Code

Copy the following code to your Arduino IDE, or to the main.cpp file if you're using VS Code. It takes a picture and sends it to Firebase when it first boots. /********* Rui Santos Complete instructions at: https://RandomNerdTutorials.com/esp32-cam-save-picture-firebase-storage/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Based on the example provided by the ESP Firebase Client Library *********/ #include "Arduino.h" #include "WiFi.h" #include "esp_camera.h" #include "soc/soc.h" // Disable brownout problems #include "soc/rtc_cntl_reg.h" // Disable brownout problems #include "driver/rtc_io.h" #include <LittleFS.h> #include <FS.h> #include <Firebase_ESP_Client.h> //Provide the token generation process info. #include <addons/TokenHelper.h> //Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; // Insert Firebase project API Key #define API_KEY "REPLACE_WITH_YOUR_FIREBASE_PROJECT_API_KEY" // Insert Authorized Email and Corresponding Password #define USER_EMAIL "REPLACE_WITH_THE_AUTHORIZED_USER_EMAIL" #define USER_PASSWORD "REPLACE_WITH_THE_AUTHORIZED_USER_PASSWORD" // Insert Firebase storage bucket ID e.g bucket-name.appspot.com #define STORAGE_BUCKET_ID "REPLACE_WITH_YOUR_STORAGE_BUCKET_ID" // For example: //#define STORAGE_BUCKET_ID "esp-iot-app.appspot.com" // Photo File Name to save in LittleFS #define FILE_PHOTO_PATH "/photo.jpg" #define BUCKET_PHOTO "/data/photo.jpg" // OV2640 camera module pins (CAMERA_MODEL_AI_THINKER) #define PWDN_GPIO_NUM 32 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 0 #define SIOD_GPIO_NUM 26 #define SIOC_GPIO_NUM 27 #define Y9_GPIO_NUM 35 #define Y8_GPIO_NUM 34 #define Y7_GPIO_NUM 39 #define Y6_GPIO_NUM 36 #define Y5_GPIO_NUM 21 #define Y4_GPIO_NUM 19 #define Y3_GPIO_NUM 18 #define Y2_GPIO_NUM 5 #define VSYNC_GPIO_NUM 25 #define HREF_GPIO_NUM 23 #define PCLK_GPIO_NUM 22 boolean takeNewPhoto = true; //Define Firebase Data objects FirebaseData fbdo; FirebaseAuth auth; FirebaseConfig configF; void fcsUploadCallback(FCS_UploadStatusInfo info); bool taskCompleted = false; // Capture Photo and Save it to LittleFS void capturePhotoSaveLittleFS( void ) { // Dispose first pictures because of bad quality camera_fb_t* fb = NULL; // Skip first 3 frames (increase/decrease number as needed). for (int i = 0; i < 4; i++) { fb = esp_camera_fb_get(); esp_camera_fb_return(fb); fb = NULL; } // Take a new photo fb = NULL; fb = esp_camera_fb_get(); if(!fb) { Serial.println("Camera capture failed"); delay(1000); ESP.restart(); } // Photo file name Serial.printf("Picture file name: %s\n", FILE_PHOTO_PATH); File file = LittleFS.open(FILE_PHOTO_PATH, FILE_WRITE); // Insert the data in the photo file if (!file) { Serial.println("Failed to open file in writing mode"); } else { file.write(fb->buf, fb->len); // payload (image), payload length Serial.print("The picture has been saved in "); Serial.print(FILE_PHOTO_PATH); Serial.print(" - Size: "); Serial.print(fb->len); Serial.println(" bytes"); } // Close the file file.close(); esp_camera_fb_return(fb); } void initWiFi(){ WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connecting to WiFi..."); } } void initLittleFS(){ if (!LittleFS.begin(true)) { Serial.println("An Error has occurred while mounting LittleFS"); ESP.restart(); } else { delay(500); Serial.println("LittleFS mounted successfully"); } } void initCamera(){ // OV2640 camera module camera_config_t config; config.ledc_channel = LEDC_CHANNEL_0; config.ledc_timer = LEDC_TIMER_0; config.pin_d0 = Y2_GPIO_NUM; config.pin_d1 = Y3_GPIO_NUM; config.pin_d2 = Y4_GPIO_NUM; config.pin_d3 = Y5_GPIO_NUM; config.pin_d4 = Y6_GPIO_NUM; config.pin_d5 = Y7_GPIO_NUM; config.pin_d6 = Y8_GPIO_NUM; config.pin_d7 = Y9_GPIO_NUM; config.pin_xclk = XCLK_GPIO_NUM; config.pin_pclk = PCLK_GPIO_NUM; config.pin_vsync = VSYNC_GPIO_NUM; config.pin_href = HREF_GPIO_NUM; config.pin_sscb_sda = SIOD_GPIO_NUM; config.pin_sscb_scl = SIOC_GPIO_NUM; config.pin_pwdn = PWDN_GPIO_NUM; config.pin_reset = RESET_GPIO_NUM; config.xclk_freq_hz = 20000000; config.pixel_format = PIXFORMAT_JPEG; config.grab_mode = CAMERA_GRAB_LATEST; if (psramFound()) { config.frame_size = FRAMESIZE_UXGA; config.jpeg_quality = 10; config.fb_count = 1; } else { config.frame_size = FRAMESIZE_SVGA; config.jpeg_quality = 12; config.fb_count = 1; } // Camera init esp_err_t err = esp_camera_init(&config); if (err != ESP_OK) { Serial.printf("Camera init failed with error 0x%x", err); ESP.restart(); } } void setup() { // Serial port for debugging purposes Serial.begin(115200); initWiFi(); initLittleFS(); // Turn-off the 'brownout detector' WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); initCamera(); //Firebase // Assign the api key configF.api_key = API_KEY; //Assign the user sign in credentials auth.user.email = USER_EMAIL; auth.user.password = USER_PASSWORD; //Assign the callback function for the long running token generation task configF.token_status_callback = tokenStatusCallback; //see addons/TokenHelper.h Firebase.begin(&configF, &auth); Firebase.reconnectWiFi(true); } void loop() { if (takeNewPhoto) { capturePhotoSaveLittleFS(); takeNewPhoto = false; } delay(1); if (Firebase.ready() && !taskCompleted){ taskCompleted = true; Serial.print("Uploading picture... "); //MIME type should be valid to avoid the download problem. //The file systems for flash and SD/SDMMC can be changed in FirebaseFS.h. if (Firebase.Storage.upload(&fbdo, STORAGE_BUCKET_ID /* Firebase Storage bucket id */, FILE_PHOTO_PATH /* path to local file */, mem_storage_type_flash /* memory storage type, mem_storage_type_flash and mem_storage_type_sd */, BUCKET_PHOTO /* path of remote file stored in the bucket */, "image/jpeg" /* mime type */,fcsUploadCallback)){ Serial.printf("\nDownload URL: %s\n", fbdo.downloadURL().c_str()); } else{ Serial.println(fbdo.errorReason()); } } } // The Firebase Storage upload callback function void fcsUploadCallback(FCS_UploadStatusInfo info){ if (info.status == firebase_fcs_upload_status_init){ Serial.printf("Uploading file %s (%d) to %s\n", info.localFileName.c_str(), info.fileSize, info.remoteFileName.c_str()); } else if (info.status == firebase_fcs_upload_status_upload) { Serial.printf("Uploaded %d%s, Elapsed time %d ms\n", (int)info.progress, "%", info.elapsedTime); } else if (info.status == firebase_fcs_upload_status_complete) { Serial.println("Upload completed\n"); FileMetaInfo meta = fbdo.metaData(); Serial.printf("Name: %s\n", meta.name.c_str()); Serial.printf("Bucket: %s\n", meta.bucket.c_str()); Serial.printf("contentType: %s\n", meta.contentType.c_str()); Serial.printf("Size: %d\n", meta.size); Serial.printf("Generation: %lu\n", meta.generation); Serial.printf("Metageneration: %lu\n", meta.metageneration); Serial.printf("ETag: %s\n", meta.etag.c_str()); Serial.printf("CRC32: %s\n", meta.crc32.c_str()); Serial.printf("Tokens: %s\n", meta.downloadTokens.c_str()); Serial.printf("Download URL: %s\n\n", fbdo.downloadURL().c_str()); } else if (info.status == firebase_fcs_upload_status_error){ Serial.printf("Upload failed, %s\n", info.errorMsg.c_str()); } } View raw code You need to insert your network credentials, storage bucket ID, and project API key for the project to work. This sketch was based on a basic example provided by the library. You can find more examples here.

How the Code Works

Continue reading to learn how the code works or skip to the demonstration section.

Libraries

First, include the required libraries. #include "WiFi.h" #include "esp_camera.h" #include "Arduino.h" #include "soc/soc.h" // Disable brownout problems #include "soc/rtc_cntl_reg.h" // Disable brownout problems #include "driver/rtc_io.h" #include <LittleFS.h> #include <FS.h> #include <Firebase_ESP_Client.h> //Provide the token generation process info. #include <addons/TokenHelper.h>

Network Credentials

Insert your network credentials in the following variables so that the ESP can connect to the internet and communicate with Firebase. //Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Firebase Project API Key

Insert your Firebase project API keysee this section: 4) Get Project API Key. // Insert Firebase project API Key #define API_KEY "REPLACE_WITH_YOUR_FIREBASE_PROJECT_API_KEY."

User Email and Password

Insert the authorized email and the corresponding passwordsee this section: 2) Set Authentication Methods. #define USER_EMAIL "REPLACE_WITH_THE_AUTHORIZED_USER_EMAIL" #define USER_PASSWORD "REPLACE_WITH_THE_AUTHORIZED_USER_PASSWORD"

Firebase Storage Bucket ID

Insert the Firebase storage bucket ID, e.g bucket-name.appspot.com. In my case, it is esp-firebase-demo.appspot.com. (remove any slashes / at the end or at the beginning of the bucket ID, otherwise, it will not work). define STORAGE_BUCKET_ID "REPLACE_WITH_YOUR_STORAGE_BUCKET_ID"

Picture Path

The FILE_PHOTO_PATH variable defines the LittleFS path where the picture will be saved. It will be saved with the name photo.jpg. #define FILE_PHOTO "/photo.jpg" We also have a variable to hold the path where the picture will be saved on the Storage Bucket on Firebase. #define BUCKET_PHOTO "/data/photo.jpg"

ESP32-CAM Pin Definition

The following lines define the ESP32-CAM pins. This is the definition for the ESP32-CAM AI-Thinker module. If you're using another ESP32-CAM module, you need to modify the pin definitioncheck this tutorial: ESP32-CAM Camera Boards: Pin and GPIOs Assignment Guide. // OV2640 camera module pins (CAMERA_MODEL_AI_THINKER) #define PWDN_GPIO_NUM 32 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 0 #define SIOD_GPIO_NUM 26 #define SIOC_GPIO_NUM 27 #define Y9_GPIO_NUM 35 #define Y8_GPIO_NUM 34 #define Y7_GPIO_NUM 39 #define Y6_GPIO_NUM 36 #define Y5_GPIO_NUM 21 #define Y4_GPIO_NUM 19 #define Y3_GPIO_NUM 18 #define Y2_GPIO_NUM 5 #define VSYNC_GPIO_NUM 25 #define HREF_GPIO_NUM 23 #define PCLK_GPIO_NUM 22

Other Variables

The takeNewPhoto variable checks if it is time to take a new photo. We'll set it to true, so that it takes a picture when the board first runs. boolean takeNewPhoto = true; Then, we define Firebase configuration data objects. //Define Firebase Data objects FirebaseData fbdo; FirebaseAuth auth; FirebaseConfig configF; The taskCompleted is a boolean variable that checks if we successfully connected to Firebase. bool taskCompleted = false;

capturePhotoSaveLittleFS() Function

The capturePhotoSaveLittleFS() function takes a photo and saves it in the ESP32 filesystem. // Capture Photo and Save it to LittleFS void capturePhotoSaveLittleFS( void ) { // Dispose first pictures because of bad quality camera_fb_t* fb = NULL; // Skip first 3 frames (increase/decrease number as needed). for (int i = 0; i < 4; i++) { fb = esp_camera_fb_get(); esp_camera_fb_return(fb); fb = NULL; } // Take a new photo fb = NULL; fb = esp_camera_fb_get(); if(!fb) { Serial.println("Camera capture failed"); delay(1000); ESP.restart(); } // Photo file name Serial.printf("Picture file name: %s\n", FILE_PHOTO_PATH); File file = LittleFS.open(FILE_PHOTO_PATH, FILE_WRITE); // Insert the data in the photo file if (!file) { Serial.println("Failed to open file in writing mode"); } else { file.write(fb->buf, fb->len); // payload (image), payload length Serial.print("The picture has been saved in "); Serial.print(FILE_PHOTO_PATH); Serial.print(" - Size: "); Serial.print(fb->len); Serial.println(" bytes"); } // Close the file file.close(); esp_camera_fb_return(fb); }

initWiFi() Function

The initWiFi() function initializes Wi-Fi. void initWiFi(){ WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connecting to WiFi..."); } }

initLittleFS() Function

The initLittleFS() function initializes the LittleFS filesystem. void initLittleFS(){ if (!LittleFS.begin(true)) { Serial.println("An Error has occurred while mounting LittleFS"); ESP.restart(); } else { delay(500); Serial.println("LittleFS mounted successfully"); } }

initCamera() Function

The initCamera() function initializes the ESP32-CAM. void initCamera(){ // OV2640 camera module camera_config_t config; config.ledc_channel = LEDC_CHANNEL_0; config.ledc_timer = LEDC_TIMER_0; config.pin_d0 = Y2_GPIO_NUM; config.pin_d1 = Y3_GPIO_NUM; config.pin_d2 = Y4_GPIO_NUM; config.pin_d3 = Y5_GPIO_NUM; config.pin_d4 = Y6_GPIO_NUM; config.pin_d5 = Y7_GPIO_NUM; config.pin_d6 = Y8_GPIO_NUM; config.pin_d7 = Y9_GPIO_NUM; config.pin_xclk = XCLK_GPIO_NUM; config.pin_pclk = PCLK_GPIO_NUM; config.pin_vsync = VSYNC_GPIO_NUM; config.pin_href = HREF_GPIO_NUM; config.pin_sscb_sda = SIOD_GPIO_NUM; config.pin_sscb_scl = SIOC_GPIO_NUM; config.pin_pwdn = PWDN_GPIO_NUM; config.pin_reset = RESET_GPIO_NUM; config.xclk_freq_hz = 20000000; config.pixel_format = PIXFORMAT_JPEG; if (psramFound()) { config.frame_size = FRAMESIZE_UXGA; config.jpeg_quality = 10; config.fb_count = 2; } else { config.frame_size = FRAMESIZE_SVGA; config.jpeg_quality = 12; config.fb_count = 1; } // Camera init esp_err_t err = esp_camera_init(&config); if (err != ESP_OK) { Serial.printf("Camera init failed with error 0x%x", err); ESP.restart(); } }

setup()

In the setup(), initialize the Serial Monitor, Wi-Fi, LittleFS, and the camera. // Serial port for debugging purposes Serial.begin(115200); initWiFi(); initLittleFS(); // Turn-off the 'brownout detector' WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); initCamera(); Then, assign the following settings to the Firebase configuration objects. // Assign the api key configF.api_key = API_KEY; //Assign the user sign in credentials auth.user.email = USER_EMAIL; auth.user.password = USER_PASSWORD; //Assign the callback function for the long running token generation task configF.token_status_callback = tokenStatusCallback; //see addons/TokenHelper.h Finally, initialize Firebase. Firebase.begin(&configF, &auth); Firebase.reconnectWiFi(true);

loop()

In the loop(), take a new picture and save it to the filesystem. if (takeNewPhoto) { capturePhotoSaveLittleFS(); takeNewPhoto = false; } Finally, send the picture to Firebase. if (Firebase.ready() && !taskCompleted){ taskCompleted = true; Serial.print("Uploading picture... "); //MIME type should be valid to avoid the download problem. //The file systems for flash and SD/SDMMC can be changed in FirebaseFS.h. if (Firebase.Storage.upload(&fbdo, STORAGE_BUCKET_ID /* Firebase Storage bucket id */, FILE_PHOTO_PATH /* path to local file */, mem_storage_type_flash /* memory storage type, mem_storage_type_flash and mem_storage_type_sd */, BUCKET_PHOTO /* path of remote file stored in the bucket */, "image/jpeg" /* mime type */,fcsUploadCallback)){ Serial.printf("\nDownload URL: %s\n", fbdo.downloadURL().c_str()); } else{ Serial.println(fbdo.errorReason()); } } The command that actually sends the picture is Firebase.Storage.upload(): Firebase.Storage.upload(&fbdo, STORAGE_BUCKET_ID, FILE_PHOTO, mem_storage_type_flash, FILE_PHOTO, "image/jpeg", fcsUploadCallback) This function returns a boolean variable indicating the success of the operation. It accepts as the second argument, the storage bucket ID. Then, the path where the file is saved; the storage type (it can be LittleFS or SD Card); the path where the file will be saved in the Firebase storage; the mime type, and a callback function (fcsUploadCallback) (this callback function simply inform us of the uploading process state). // The Firebase Storage upload callback function void fcsUploadCallback(FCS_UploadStatusInfo info){ if (info.status == firebase_fcs_upload_status_init){ Serial.printf("Uploading file %s (%d) to %s\n", info.localFileName.c_str(), info.fileSize, info.remoteFileName.c_str()); } else if (info.status == firebase_fcs_upload_status_upload) { Serial.printf("Uploaded %d%s, Elapsed time %d ms\n", (int)info.progress, "%", info.elapsedTime); } else if (info.status == firebase_fcs_upload_status_complete) { Serial.println("Upload completed\n"); FileMetaInfo meta = fbdo.metaData(); Serial.printf("Name: %s\n", meta.name.c_str()); Serial.printf("Bucket: %s\n", meta.bucket.c_str()); Serial.printf("contentType: %s\n", meta.contentType.c_str()); Serial.printf("Size: %d\n", meta.size); Serial.printf("Generation: %lu\n", meta.generation); Serial.printf("Metageneration: %lu\n", meta.metageneration); Serial.printf("ETag: %s\n", meta.etag.c_str()); Serial.printf("CRC32: %s\n", meta.crc32.c_str()); Serial.printf("Tokens: %s\n", meta.downloadTokens.c_str()); Serial.printf("Download URL: %s\n\n", fbdo.downloadURL().c_str()); } else if (info.status == firebase_fcs_upload_status_error){ Serial.printf("Upload failed, %s\n", info.errorMsg.c_str()); }

Create a Wi-Fi Manager (AsyncWebServer library)

In this guide, you'll create and set up a Wi-Fi Manager with the ESPAsyncWebServer library that you can modify to use with your web server projects or with any project that needs a connection to a Wi-Fi network. The Wi-Fi Manager allows you to connect the ESP32 board to different Access Points (networks) without hard-coding network credentials (SSID and password) and upload new code to your board. Your ESP will automatically join the last saved network or set up an Access Point that you can use to configure the network credentials. To better understand how this project works, we recommend taking a look at the following tutorials: Input Data on HTML Form ESP32/ESP8266 Web Server using Arduino IDE How to Set an ESP32 Access Point (AP) for Web Server ESP32 Static/Fixed IP Address We also use this Wi-Fi Manager approach on the following project: ESP32 Neopixel Status Indicator and Sensor PCB Shield with Wi-Fi Manager

How it Works

Take a look at the following diagram to understand how the Wi-Fi Manager we'll create works. When the ESP first starts, it tries to read the ssid.txt, pass.txt and ip.txt files* (1); If the files are empty (2) (the first time you run the board, the files are empty), your board is set as an access point (3); Using any Wi-Fi enabled device with a browser, you can connect to the newly created Access Point (default name ESP-WIFI-MANAGER); After establishing a connection with the ESP-WIFI-MANAGER, you can go to the default IP address 192.168.4.1 to open a web page that allows you to configure your SSID and password (4); The SSID, password, and IP address inserted in the form are saved in the corresponding files: ssid.txt, pass.txt, and ip.txt (5); After that, the ESP board restarts (6); This time, after restarting, the files are not empty, so the ESP will try to connect to the network in station mode using the settings you've inserted in the form (7); If it establishes a connection, the process is completed successfully, and you can access the main web server page that can do whatever you want (control sensor readings, control outputs, display some text, etc.) (9). Otherwise, it will set the Access Point (3), and you can access the default IP address (192.168.4.1) to add another SSID/password combination. * we also created a gateway field and a gateway.txt file to save the IP address gateway (this is not shown in the diagram). To show you how to set the Wi-Fi Manager, we'll set up a web server that controls one output (GPIO2the built-in LED). You can apply the Wi-Fi Manager to any web server project built with the ESPAsyncWebServer library or to any project that requires the ESP to be connected to a wi-fi network.

Prerequisites

We'll program the ESP32 board using Arduino IDE. So make sure you have the ESP32 board add-on installed. Installing ESP32 Board in Arduino IDE (Windows, Mac OS X, and Linux) If you want to program the ESP32 using VS Code + PlatformIO, follow the next tutorial: Getting Started with VS Code and PlatformIO IDE for ESP32 and ESP8266 (Windows, Mac OS X, Linux Ubuntu)

Installing Libraries (Arduino IDE)

You need to install the following libraries in your Arduino IDE to build the web server for this project. ESPAsyncWebServer (.zip folder) AsyncTCP (.zip folder) The ESPAsyncWebServer, AsynTCP, and ESPAsyncTCP libraries aren't available to install through the Arduino Library Manager, so you need to copy the library files to the Arduino installation Libraries folder. Alternatively, in your Arduino IDE, you can go to Sketch > Include Library > Add .zip Library and select the libraries you've just downloaded.

Installing Libraries (VS Code + PlatformIO)

If you're programming the ESP32 using PlatformIO, copy the following to the platformio.ini to include the ESPAsyncWebServer library (it will automatically include any dependencies like the AsynTCP or ESPAsyncTCP libraries) and change the baud rate to 115200: monitor_speed = 115200 lib_deps = ESP Async WebServer

Filesystem Uploader

Before proceeding, you need to have the ESP32 Uploader Plugin installed in your Arduino IDE. Install ESP32 Filesystem Uploader in Arduino IDE If you're using VS Code with PlatformIO, follow the next tutorials to learn how to upload files to the filesystem: ESP32 with VS Code and PlatformIO: Upload Files to Filesystem (SPIFFS)

Organizing your Files

To keep the project organized and make it easier to understand, we'll create four different files to build the web server: Arduino sketch that handles the web server; index.html: to define the content of the web page in station mode to control the output (or any other web page you want to build); style.css: to style the web pages; wifimanager.html: to define the web page's content to display the Wi-Fi Manager when the ESP is in access point mode. You should save the HTML and CSS files inside a folder called data inside the Arduino sketch folder, as shown in the previous diagram. We'll upload these files to the ESP32 filesystem (SPIFFS). You can download all project files: Download All the Arduino Project Files

Creating the HTML Files

For this project, you need two HTML files. One to build the main page that controls the output (index.html) and another to build the Wi-Fi Manager page (wifimanager.html).

index.html

Here's the text you should copy to your index.html file. <!DOCTYPE html> <html> <head> <title>ESP WEB SERVER</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" type="text/css" href="style.css"> <link rel="icon" type="image/png" href="favicon.png"> <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous"> </head> <body> <div> <h1>ESP WEB SERVER</h2> </div> <div> <div> <div> <p><i></i> GPIO 2</p> <p> <a href="on"><button>ON</button></a> <a href="off"><button>OFF</button></a> </p> <p>State: %STATE%</p> </div> </div> </div> </body> </html> View raw code We won't explain how this HTML file works because that's not the purpose of this tutorial. The purpose of this tutorial is to explain the parts related to the Wi-Fi Manager.

wifimanager.html

The Wi-Fi Manager web page looks like this: Copy the following to the wifimanager.html file. This creates a web page with a form with three input fields and a Submit button. <!DOCTYPE html> <html> <head> <title>ESP Wi-Fi Manager</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" href="data:,"> <link rel="stylesheet" type="text/css" href="style.css"> </head> <body> <div> <h1>ESP Wi-Fi Manager</h2> </div> <div> <div> <div> <form action="/" method="POST"> <p> <label for="ssid">SSID</label> <input type="text" id ="ssid" name="ssid"><br> <label for="pass">Password</label> <input type="text" id ="pass" name="pass"><br> <label for="ip">IP Address</label> <input type="text" id ="ip" name="ip" value="192.168.1.200"><br> <label for="gateway">Gateway Address</label> <input type="text" id ="gateway" name="gateway" value="192.168.1.1"><br> <input type ="submit" value ="Submit"> </p> </form> </div> </div> </div> </body> </html> View raw code In this HTML file, we create an HTML form that will make an HTTP POST request with the data submitted to the server. <form action="/" method="POST"> The form contains three input fields and corresponding labels: SSID, password, and IP address. This is the input field for the SSID: <label for="ssid">SSID</label> <input type="text" id ="ssid" name="ssid"><br> This is the input field for the password. <label for="pass">Password</label> <input type="text" id ="pass" name="pass"><br> There is an input field for the IP address that you want to attribute to the ESP in station mode. As default, we set it to 192.168.1.200 (you can set another default IP address, or you can delete the value parameterit won't have a default value). <input type="text" id ="ip" name="ip" value="192.168.1.200"> Finally, there's an input field for the gateway address. If the default IP address is 192.168.1.200, the gateway can be 192.168.1.1 by default. <input type="text" id ="gateway" name="gateway" value="192.168.1.1"><br>

CSS File

Copy the following styles to your style.css file. We won't explain how these styles work. We have already explained how similar styles work in other ESP Web Server projects. html { font-family: Arial, Helvetica, sans-serif; display: inline-block; text-align: center; } h1 { font-size: 1.8rem; color: white; } p { font-size: 1.4rem; } .topnav { overflow: hidden; background-color: #0A1128; } body { margin: 0; } .content { padding: 5%; } .card-grid { max-width: 800px; margin: 0 auto; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); } .card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); } .card-title { font-size: 1.2rem; font-weight: bold; color: #034078 } input[type=submit] { border: none; color: #FEFCFB; background-color: #034078; padding: 15px 15px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; width: 100px; margin-right: 10px; border-radius: 4px; transition-duration: 0.4s; } input[type=submit]:hover { background-color: #1282A2; } input[type=text], input[type=number], select { width: 50%; padding: 12px 20px; margin: 18px; display: inline-block; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } label { font-size: 1.2rem; } .value{ font-size: 1.2rem; color: #1282A2; } .state { font-size: 1.2rem; color: #1282A2; } button { border: none; color: #FEFCFB; padding: 15px 32px; text-align: center; font-size: 16px; width: 100px; border-radius: 4px; transition-duration: 0.4s; } .button-on { background-color: #034078; } .button-on:hover { background-color: #1282A2; } .button-off { background-color: #858585; } .button-off:hover { background-color: #252524; }

Firebase Web App to Display Sensor Readings (with Authentication)

In this guide, you'll create a Firebase Web App to display sensor readings saved on the Firebase Realtime Database. The sensor readings web page is protected with authentication with email and password. You'll learn how to display data from the database and how to add authentication to your web app. This article is Part 2 of this previous tutorial: ESP32/ESP8266 Firebase: Send BME280 Sensor Readings to the Realtime Database. Follow that tutorial first, before proceeding.

Project Overview

In this tutorial (Part 2), you'll create a web app to display the sensor readings saved on the Firebase Realtime Database (read this previous tutorial). The following diagram shows a high-level overview of the project we'll buildprogramming the ESP32/ESP8266 and setting up the Firebase Project was done in Part 1. Firebase hosts your web app over a global CDN using Firebase Hosting and provides an SSL certificate. You can access your web app from anywhere using the Firebase-generated domain name. When you first access the web app, you need to authenticate with an authorized email address and password. You already set up that user and the authentication method in Part 1. After authentication, you can access a web app page that shows the sensor readings saved on the realtime database. The realtime database was set up on Part 1. Once you're logged in, you can logout any time. The next time you'll acces the app you'll need to login again.

Prerequisites

Before start creating the Firebase Web App, you need to check the following prerequisites.

Creating a Firebase Project

You should have followed the following tutorial first: ESP32/ESP8266 Firebase: Send BME280 Sensor Readings to the Realtime Database The ESP32/ESP8266 must be running the code provided in that tutorial. The realtime database and authentication must be set up also as shown in the tutorial.

Install Required Software

Before getting started you need to install the required software to create the Firebase Web App. Here's a list of the software you need to install (click on the links for instructions): Visual Studio Code Node.JS LTS version Install Firebase Tools

1) Add an App to Your Firebase Project

1) Go to your Firebase project Console and add an app to your project by clicking on the +Add app button. 2) Select the web app icon. 3) Give your app a name. Then, check the box next to Also set up Firebase Hosting for this App. Click Register app. 4) Then, copy the firebaseConfig object and save it because you'll need it later. After this, you can also access the firebaseConfig object if you go to your Project settings in your Firebase console. 5) Click Next on the proceeding steps, and finally on Continue to console.

2) Setting Up a Firebase Web App Project (VS Code)

Follow the next steps to create a Firebase Web App Project using VS Code.

1) Creating a Project Folder

1) Create a folder on your computer where you want to save your Firebase projectfor example, Firebase-Project on the Desktop. 2) Open VS Code. Go to File > Open Folder and select the folder you've just created. 3) Go to Terminal > New Terminal. A new Terminal window should open on your project path.

2) Firebase Login

4) On the previous Terminal window, type the following: firebase login 5) You'll be asked to collect CLI usage and error reporting information. Enter n and press Enter to deny. Note: If you are already logged in, it will show a message saying: Already logged in as [email protected]. 6) After this, it will pop up a new window on your browser to login into your firebase account. 7) Allow Firebase CLI to access your google account. 8) After this, Firebase CLI login should be successful. You can close the browser window.

3) Initializing Web App Firebase Project

9) After successfully login in, run the following command to start a Firebase project directory in the current folder. firebase init 10) You'll be asked if you want to initialize a Firebase project in the current directory. Enter Y and hit Enter. 11) Then, use up and down arrows and the Space key to select the options. Select the following options: Realtime Database: Configure security rules file for Realtime Database and (optionally) provision default instance. Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys The selected options will show up with a green asterisk. Then, hit Enter. 12) Select the option Use an existing projectit should be highlighted in bluethen, hit Enter. 13) After that, select the Firebase project for this directoryit should be the project created in this previous tutorial. In my case, it is called esp-firebase-demo. Then hit Enter. 14) Press Enter on the following question to select the default database security rules file: What file should be used for Realtime Database Security Rules? 15) Then, select the hosting options as shown below: What do you want to use as your public directory? Hit Enter to select public. Configure as a single-page app (rewrite urls to /index.html)? No Set up automatic builds and deploys with GitHub? No 16) The Firebase project should now be initialized successfully. Notice that VS code created some essential files under your project folder. The index.html file contains some HTML text to build a web page. For now, leave the default HTML text. The idea is to replace that with your own HTML text to build a custom web page for your needs. We'll do that later in this tutorial. 17) To check if everything went as expected, run the following command on the VS Code Terminal window. firebase deploy You should get a Deploy complete! message and an URL to the Project Console and the Hosting URL. 18) Copy the hosting URL and paste it into a web browser window. You should see the following web page. You can access that web page from anywhere in the world. The web page you've seen previously is built with the HTML file placed in the public folder of your Firebase project. By changing the content of that file, you can create your own web app. That's what we're going to do in the next section.

3) Creating Firebase Web App

Now that you've created a Firebase project app successfully on VS Code, follow the next steps to customize the app to display the sensor readings on a login-protected web page.

index.html

Copy the following to your index.html file. This HTML file creates a simple web page that displays the readings saved on the Realtime Database created on this previous project. If you aren't authenticated, it shows a login form. When you authenticate with an authorized user email and corresponding password, it shows the user interface with the sensor readings. <!-- Complete Project Details at: https://RandomNerdTutorials.com/ --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>ESP IoT Firebase App</title> <!-- update the version number as needed --> <script src="https://www.gstatic.com/firebasejs/8.8.1/firebase-app.js"></script> <!-- include only the Firebase features as you need --> <script src="https://www.gstatic.com/firebasejs/8.8.1/firebase-auth.js"></script> <script src="https://www.gstatic.com/firebasejs/8.8.1/firebase-database.js"></script> <script> // REPLACE WITH YOUR web app's Firebase configuration const firebaseConfig = { apiKey: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", authDomain: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", databaseURL: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", projectId: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", storageBucket: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", messagingSenderId: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", appId: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION" }; // Initialize firebase firebase.initializeApp(firebaseConfig); // Make auth and database references const auth = firebase.auth(); const db = firebase.database(); </script> <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous"> <link rel="icon" type="image/png" href="favicon.png"> <link rel="stylesheet" type="text/css" href="style.css"> </head> <body> <!--TOP BAR--> <div> <h1>Sensor Readings App <i></i></h2> </div> <!--AUTHENTICATION BAR (USER DETAILS/LOGOUT BUTTON)--> <div style="display: none;"> <p><span>User logged in</span> <span>USEREMAIL</span> <a href="/">(logout)</a> </p> </div> <!--LOGIN FORM--> <form style="display: none;"> <div> <label for="input-email"><b>Email</b></label> <input type="text" placeholder="Enter Username" required> <label for="input-password"><b>Password</b></label> <input type="password" placeholder="Enter Password" required> <button type="submit">Login</button> <p style="color:red;"></p> </div> </form> <!--CONTENT (SENSOR READINGS)--> <div style="display: none;"> <div> <!--TEMPERATURE--> <div> <p><i style="color:#059e8a;"></i> TEMPERATURE</p> <p><span><span></span> °C</span></p> </div> <!--HUMIDITY--> <div> <p><i style="color:#00add6;"></i> HUMIDITY</p> <p><span><span></span> %</span></p> </div> <!--PRESSURE--> <div> <p><i style="color:#e1e437;"></i> PRESSURE</p> <p><span><span></span> hPa</span></p> </div> </div> </div> <script src="scripts/auth.js"></script> <script src="scripts/index.js"></script> </body> </html> View raw code You need to modify the code with your own firebaseConfig objectthe one you've got in this step.

How it Works

Let's take a quick look at the HTML file, or skip to the next section. In the <head> of the HTML file, we must add all the required metadata. The title of the web page is ESP Firebase App, but you can change it in the following line. <title>ESP Firebase App</title> You must add the following line to be able to use Firebase with your app. <script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js"></script> You must also add any Firebase products you want to use. In this example, we're using the Realtime Database and Authentication. <script src="https://www.gstatic.com/firebasejs/8.8.1/firebase-auth.js"></script> <script src="https://www.gstatic.com/firebasejs/8.8.1/firebase-database.js"></script> Then, replace the firebaseConfig object with the one you've gotten from this step. const firebaseConfig = { apiKey: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", authDomain: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", databaseURL: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", projectId: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", storageBucket: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", messagingSenderId: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION", appId: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION" }; Finally, Firebase is initialized, and we create two global variables db and auth that refer to Firebase authentication and to Firebase realtime database. // Initialize firebase firebase.initializeApp(firebaseConfig); // Make auth and database references const auth = firebase.auth(); const db = firebase.database(); The following line allows us to use fontawesome icons: <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous"> The next includes a favicon on our web page. <link rel="icon" type="image/png" href="favicon.png"> Finally, reference an external style.css file to format the HTML page. <link rel="stylesheet" type="text/css" href="style.css"> We're done with the metadata. Now, let's go to the HTML parts that are visible to the usergo between the <body> and </body> tags. We create a top navigation bar with the name of our app and a small icon from fontawesome. <div> <h1>Sensor Readings App <i></i></h2> </div> The following lines create a bar with the details of the authenticated user (email). It also shows a logout link to log out the user. <div style="display: none;"> <p><span>User logged in</span> <span>USEREMAIL</span> <a href="/">(logout)</a> </p> </div> First, we set the display style of all elements to none. We'll hide and show content depending if the user is authenticated or notwe'll handle that using JavaScript. Next, the following lines create the login form with an input field for the email and an input field for the password: <form style="display: none;"> <div> <label for="input-email"><b>Email</b></label> <input type="text" placeholder="Enter Username" required> <label for="input-password"><b>Password</b></label> <input type="password" placeholder="Enter Password" required> <button type="submit">Login</button> <p style="color:red;"></p> </div> </form> Inside the form, there's also a paragraph to display an error message if the login fails. <p style="color:red;"></p> Finally, we create a grid to display the sensor readings. <!--CONTENT (SENSOR READINGS)--> <div style="display: none;"> <div> <!--TEMPERATURE--> <div> <p><i style="color:#059e8a;"></i> TEMPERATURE</p> <p><span><span></span> °C</span></p> </div> <!--HUMIDITY--> <div> <p><i style="color:#00add6;"></i> HUMIDITY</p> <p><span><span></span> %</span></p> </div> <!--PRESSURE--> <div> <p><i style="color:#e1e437;"></i> PRESSURE</p> <p><span><span></span> hPa</span></p> </div> </div> </div> The places where we'll insert the sensor readings have <span> tags with specific ids so that we can refer to those HTML elements using JavaScript and insert sensor readings saved on the database. temperature: id = temp humidity: id = hum pressure: id = pres Finally, we need to add references to the external JavaScript files. For our application, we'll create two JavaScript files: auth.js (that handles everything related to the authentication) and index.js that handles everything related to the UI. We'll create those files inside a folder called scripts inside the public folder of our application. <script src="scripts/auth.js"></script> <script src="scripts/index.js"></script> After making the necessary changes (inserting your firebaseConfig object), you can save the HTML file.

style.css

Inside the public folder create a file called style.css. To create the file, select the public folder, and then click on the +file icon at the top of the File Explorer. Call it style.css. Then, copy the following to the style.css file html { font-family: Verdana, Geneva, Tahoma, sans-serif; display: inline-block; text-align: center; } p { font-size: 1.2rem; } body { margin: 0; } .topnav { overflow: hidden; background-color: #049faa; color: white; font-size: 1rem; padding: 10px; } #authentication-bar{ background-color:mintcream; padding-top: 10px; padding-bottom: 10px; } #user-details{ color: cadetblue; } .content { padding: 20px; } .card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); padding: 5%; } .cards { max-width: 800px; margin: 0 auto; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); } .reading { font-size: 1.4rem; } button { background-color: #049faa; color: white; padding: 14px 20px; margin: 8px 0; border: none; cursor: pointer; border-radius: 4px; } button:hover { opacity: 0.8; } .form-elements-container{ padding: 16px; width: 250px; margin: 0 auto; } input[type=text], input[type=password] { width: 100%; padding: 12px 20px; margin: 8px 0; display: inline-block; border: 1px solid #ccc; box-sizing: border-box; } View raw code The CSS file includes some simple styles to make our webpage look better. We won't discuss how CSS works in this tutorial.

JavaScript Files

We'll create two JavaScript files (auth.js and index.js) inside a scripts folder inside the public folder. Select the public folder, then click on the +folder icon to create a new folder. Call scripts to that new folder. Then, select the scripts folder and click on the +file icon. Create a file called auth.js. Then, repeat the previous steps to create an index.js. The following image show how your web app project folder structure should look like.

auth.js

Now let's implement user sign-in using Firebase authentication. We'll implement sign-in using email and password. Copy the following to the auth.js file you created previously. // listen for auth status changes auth.onAuthStateChanged(user => { if (user) { console.log("user logged in"); console.log(user); setupUI(user); var uid = user.uid; console.log(uid); } else { console.log("user logged out"); setupUI(); } }); // login const loginForm = document.querySelector('#login-form'); loginForm.addEventListener('submit', (e) => { e.preventDefault(); // get user info const email = loginForm['input-email'].value; const password = loginForm['input-password'].value; // log the user in auth.signInWithEmailAndPassword(email, password).then((cred) => { // close the login modal & reset form loginForm.reset(); console.log(email); }) .catch((error) =>{ const errorCode = error.code; const errorMessage = error.message; document.getElementById("error-message").innerHTML = errorMessage; console.log(errorMessage); }); }); // logout const logout = document.querySelector('#logout-link'); logout.addEventListener('click', (e) => { e.preventDefault(); auth.signOut(); }); View raw code Then, save the file. This file takes care of everything related to the login and logout of the user. Continue reading to learn how the code works or skip to the next section. Login The following lines are responsible for logging in the user. const loginForm = document.querySelector('#login-form'); loginForm.addEventListener('submit', (e) => { e.preventDefault(); // get user info const email = loginForm['input-email'].value; const password = loginForm['input-password'].value; // log the user in auth.signInWithEmailAndPassword(email, password).then((cred) => { // close the login modal & reset form loginForm.reset(); console.log(email); }) .catch((error) =>{ const errorCode = error.code; const errorMessage = error.message; document.getElementById("error-message").innerHTML = errorMessage; console.log(errorMessage); }); }); We create a variable that refers to the login form HTML element called loginForm. const loginForm = document.querySelector('#login-form'); If you go back to the index.html file, you can see that the form has the login-form id. We add an event listener of type submit to the form. This means that the subsequent instructions will run whenever the form is submitted. loginForm.addEventListener('submit', (e) => { You can get the submitted data as follows. const email = loginForm['input-email'].value; const password = loginForm['input-password'].value; If you go back to the HTML file, you'll see that the input fields contain the following ids: input-email and input-password for the email and password, respectively. Now that we have the inserted email and password, we can try to log in to Firebase. To do that, pass the user's email address and password to the following method: signInWithEmailAndPassword: auth.signInWithEmailAndPassword(email, password).then((cred) => { After logging in, we reset the form and print the user email in the console. auth.signInWithEmailAndPassword(email, password).then((cred) => { // close the login modal & reset form loginForm.reset(); console.log(email); }) In case there is an error signing in, we catch the error message, and display it on the error-message HTML element (a paragraph below the form). .catch((error) =>{ const errorCode = error.code; const errorMessage = error.message; document.getElementById("error-message").innerHTML = errorMessage; console.log(errorMessage); }); Logout The following snippet is responsible for logging out the user. const logout = document.querySelector('#logout-link'); logout.addEventListener('click', (e) => { e.preventDefault(); auth.signOut(); }); When the user is logged in, a logout link is visible in the authentication bar. That link has the logout-link id (see on the HTML file). So, first, we create a variable called logout that refers to the logout link. const logout = document.querySelector('#logout-link'); Then, we add an event listener of type click. This means the subsequent instructions will run whenever you click on the logout link. logout.addEventListener('click', (e) => { When the button is clicked, we sign out the user using the signOut method. auth.signOut(); Auth State Changes To keep track of the user authentication stateto know if the user is logged in or logged out, there is a method called onAuthSateChanged that allows you to receive an event whenever the authentication state changes. auth.onAuthStateChanged(user => { if (user) { console.log("user logged in"); console.log(user); setupUI(user); var uid = user.uid; console.log(uid); } else { console.log("user logged out"); setupUI(); } }); If the user returned is null, the user is currently signed out. Otherwise, it is currently signed in. In both scenarios, we print the current user state to the console and call the setupUI() function. We haven't created that function yet (we'll create it in the next section), but it will be responsible for handling the user interface accordingly to the authentication state. When the user is logged in, we pass the user as an argument to the setupUI() function. In this case, we'll display the complete user interface to show the sensor readings, as you'll see later. if (user) { console.log("user logged in"); console.log(user); setupUI(user); We also get the user UID that we'll need later to insert and read data from the database. var uid = user.uid; console.log(uid); If the user is logged out, we call the setupUI() function without any argument. In that scenario, we'll simply display a message informing that the user is logged out and doesn't have access to the interface (as we'll see later). } else { console.log("user logged out"); setupUI(); }

index.js

The index.js file handles the UI it shows the right content depending on the user authentication status. When the user is logged in, this file gets new readings from the database whenever there's a change. Copy the following to the index.js file. const loginElement = document.querySelector('#login-form'); const contentElement = document.querySelector("#content-sign-in"); const userDetailsElement = document.querySelector('#user-details'); const authBarElement = document.querySelector("#authentication-bar"); // Elements for sensor readings const tempElement = document.getElementById("temp"); const humElement = document.getElementById("hum"); const presElement = document.getElementById("pres"); // MANAGE LOGIN/LOGOUT UI const setupUI = (user) => { if (user) { //toggle UI elements loginElement.style.display = 'none'; contentElement.style.display = 'block'; authBarElement.style.display ='block'; userDetailsElement.style.display ='block'; userDetailsElement.innerHTML = user.email; // get user UID to get data from database var uid = user.uid; console.log(uid); // Database paths (with user UID) var dbPathTemp = 'UsersData/' + uid.toString() + '/temperature'; var dbPathHum = 'UsersData/' + uid.toString() + '/humidity'; var dbPathPres = 'UsersData/' + uid.toString() + '/pressure'; // Database references var dbRefTemp = firebase.database().ref().child(dbPathTemp); var dbRefHum = firebase.database().ref().child(dbPathHum); var dbRefPres = firebase.database().ref().child(dbPathPres); // Update page with new readings dbRefTemp.on('value', snap => { tempElement.innerText = snap.val().toFixed(2); }); dbRefHum.on('value', snap => { humElement.innerText = snap.val().toFixed(2); }); dbRefPres.on('value', snap => { presElement.innerText = snap.val().toFixed(2); }); // if user is logged out } else{ // toggle UI elements loginElement.style.display = 'block'; authBarElement.style.display ='none'; userDetailsElement.style.display ='none'; contentElement.style.display = 'none'; } } View raw code Continue reading to learn how the code works or skip to the next section. Getting HTML Elements First, we create variables to refer to several elements on the UI interface by referring to their ids. To identify these elements, we recommend that you take a look at the HTML file provided and find the elements with the referred ids. const loginElement = document.querySelector('#login-form'); const contentElement = document.querySelector("#content-sign-in"); const userDetailsElement = document.querySelector('#user-details'); const authBarElement = document.querySelector("#authentication-bar"); // Elements for sensor readings const tempElement = document.getElementById("temp"); const humElement = document.getElementById("hum"); const presElement = document.getElementById("pres"); The loginElement corresponds to the login form. The contentElement corresponds to the section of the web page that is visible when the user is logged in (that shows the sensor readings). The userDetailsElement corresponds to a section that will display the email of the logged in user. The auhtBarElement corresponds to the authentication bar that shows the current user status, the email of the authenticated user and the logout link. sertupUI() Function Then, we create the setupUI() function that will handle the UI accordingly to the state of the user authentication. In the auth.js file, we called the setupUI() function with the user argument setupUI(user) if the user is logged in; or the function without argument setupUI() when the user is logged out. So, let's check what happens when the user is logged in. if (user) { We define which parts of the UI should be visible or invisible. When the user is logged in, we want to hide the login form. To hide an element, we can set the display style to none. loginElement.style.display = 'none'; We show the authentication bar (that shows the user details and the logout link). To do that, we can set its display style to block. We also want the web page's main content with the sensor readings to be visible. contentElement.style.display = 'block'; authBarElement.style.display ='block'; Finally, we can get the logged in user email with user.email and display it in the userDetailsElement section as follows: userDetailsElement.innerHTML = user.email; User UID and Database Paths After we have a logged-in user, we can get its UID with user.uid. var uid = user.uid; console.log(uid); After getting the user UID, we create variables to refer to the database paths where we save the data. // Database paths (with user UID) var dbPathTemp = 'UsersData/' + uid.toString() + '/temperature'; var dbPathHum = 'UsersData/' + uid.toString() + '/humidity'; var dbPathPres = 'UsersData/' + uid.toString() + '/pressure'; Then, we create database references to those paths. var dbRefTemp = firebase.database().ref().child(dbPathTemp); var dbRefHum = firebase.database().ref().child(dbPathHum); var dbRefPres = firebase.database().ref().child(dbPathPres); Display Sensor Readings The following lines get the new sensor readings whenever there's a change and update the corresponding HTML elements with the new values. // Update page with new readings dbRefTemp.on('value', snap => { tempElement.innerText = snap.val().toFixed(2); }); dbRefHum.on('value', snap => { humElement.innerText = snap.val().toFixed(2); }); dbRefPres.on('value', snap => { presElement.innerText = snap.val().toFixed(2); }); Logged Out UI The following snippet handles the UI when the user logs out. We want to hide the authentication bar and the main webpage content (sensor readings) and show the login form. } else{ // toggle UI elements loginElement.style.display = 'block'; authBarElement.style.display ='none'; contentElement.style.display = 'none'; }

Favicon File

To display a favicon in your web app, you need to move the picture you want to use as favicon to the public folder. The picture should be called favicon.png. You can simply drag the favicon file from your computer into the public folder in VS Code. We're using the following icon as a favicon for our web app: favicon.png

Deploy your App

After saving the HTML, CSS, and JavaScript files, deploy your app on VS Code by running the following command on the Terminal window. firebase deploy The Terminal should display something as follows: Firebase offers a free hosting service to serve your assets and web apps. Then, you can access your web app from anywhere. You can use the Hosting URL provided to access your web app from anywhere.

Demonstration

Congratulations! You successfully deployed your app. It is now hosted on a global CDN using Firebase hosting. You can access your web app from anywhere on the Hosting URL provided. In my case, it is https://esp-firebase-demo.web.app. The web app is responsive, and you can access it using your smartphone, computer, or tablet. Insert the email and password of the authorized user you added in the Firebase Authentication methods. After that, you can access the latest sensor readings. Go to your project's Firebase console Hosting tab. You can see your app domains, deploy history, and you can even roll back to previous versions of your app.

Wrapping Up

In this tutorial, you learned how to create a Firebase Web App with login/logout authentication that displays sensor readings. The sensor readings are saved on the realtime database. The database is protected using database rules (that you've already set up in the previous tutorial). You can apply what you learned here to display any other type of data, and you can change the files in the public folder to add different functionalities and features to your project.

Firebase: Send BME280 Sensor Readings to the Realtime Database

In this guide, you'll learn how to send BME280 sensor readings to the Firebase Realtime Database using the ESP32 or ESP8266 NodeMCU boards. The ESP board will authenticate as a user with email and password, and you'll add database security rules to secure your data. The boards will be programmed using the Arduino core. Here's Part 2 of this project: ESP32/ESP8266: Firebase Web App to Display Sensor Readings (with Authentication) Other Firebase Tutorials with the ESP32/ESP8266 that you might be interested in: ESP32: Getting Started with Firebase (Realtime Database) ESP8266 NodeMCU: Getting Started with Firebase (Realtime Database) ESP32 with Firebase Creating a Web App ESP8266 NodeMCU with Firebase Creating a Web App ESP32/ESP8266 Firebase Authentication (Email and Password)

What is Firebase?

Firebase is Google's mobile application development platform that helps you build, improve, and grow your app. It has many services used to manage data from any android, IOS, or web application like authentication, realtime database, hosting, etc.

Project Overview

The following diagram shows a high-level overview of the project we'll build.
    The ESP32/ESP8266 authenticates as a user with email and password (that user must be set on the Firebase authentication methods); After authentication, the ESP gets the user UID; The database is protected with security rules. The user can only access the database nodes under the node with its user UID. After getting the user UID, the ESP can publish data to the database; The ESP sends temperature, humidity and pressure to the database.
These are the main steps to complete this project:
    Create Firebase Project Set Authentication Methods Get Project API Key Set up Realtime Database Set up Database Security Rules ESP32/ESP8266 Send Sensor Readings to the Realtime Database
You can continue with the Firebase project from this previous tutorial or create a new project. If you use the Firebase project of that previous tutorial, you can skip to section 4) Set up Realtime Database because the authentication methods are already set up.

Preparing Arduino IDE

For this tutorial, we'll program the ESP32 and ESP8266 boards using the Arduino core. So, make sure you have the ESP32 or ESP8266 add-on installed in your Arduino IDE: Installing the ESP32 Board in Arduino IDE (Windows, Mac OS X, Linux) Installing ESP8266 Board in Arduino IDE (Windows, Mac OS X, Linux) If you want to program the ESP boards using VS Code with the PlatformIO extension, follow the following tutorial instead: Getting Started with VS Code and PlatformIO IDE for ESP32 and ESP8266

1) Create Firebase Proje3t

1) Go to Firebase and sign in using a Google Account. 2) Click Get Started and then Add project to create a new project. 3) Give a name to your project, for example ESP Firebase Demo. 4) Disable the option Enable Google Analytics for this project as it is not needed and click Create project. 5) It will take a few seconds to set up your project. Then, click Continue when it's ready. 6) You'll be redirected to your Project console page.

2) Set Authentication Methods

To allow authentication with email and password, first, you need to set authentication methods for your app. Most apps need to know the identity of a user. In other words, it takes care of logging in and identifying the users (in this case, the ESP32 or ESP8266). Knowing a user's identity allows an app to securely save user data in the cloud and provide the same personalized experience across all of the user's devices. To learn more about the authentication methods, you can read the documentation. 1) On the left sidebar, click on Authentication and then on Get started. 2) Select the Option Email/Password. 3) Enable that authentication method and click Save. 4) The authentication with email and password should now be enabled. 5) Now, you need to add a user. On the Authentication tab, select the Users tab at the top. Then, click on Add User. 6) Add an email address for the authorized user. It can be your google account email or any other email. You can also create an email for this specific project. Add a password that will allow you to sign in to your app and access the database. Don't forget to save the password in a safe place because you'll need it later. When you're done, click Add user. 7) A new user was successfully created and added to the Users table. Notice that Firebase creates a unique UID for each registered user. The user UID allows us to identify the user and keep track of the user to provide or deny access to the project or the database. There's also a column that registers the date of the last sign-in. At the moment, it is empty because we haven't signed in with that user yet.

3) Get Project API Key

To interface with your Firebase project using the ESP32 or ESP8266 boards, you need to get your project API key. Follow the next steps to get your project API key. 1) On the left sidebar, click on Project Settings. 2) Copy the Web API Key to a safe place because you'll need it later.

4) Set up Realtime Database

Now, let's create a realtime database and set up database rules for our project. 1) On the left sidebar, click on Realtime Database and then click on Create Database. 2) Select your database location. It should be the closest to your location. 3) Set up security rules for your database. You can select Start in test mode. We'll change the database rules in just a moment. 4) Your database is now created. You need to copy and save the database URLhighlighted in the following imagebecause you'll need it later in your ESP32/ESP8266 code.

5) Set up Database Security Rules

Now, let's set up the database rules. On the Realtime Database tab, select the Rules tab at the top. Then, click on Edit rules, copy the following rules and then click Publish. // These rules grant access to a node matching the authenticated // user's ID from the Firebase auth token { "rules": { "UsersData": { "$uid": { ".read": "$uid === auth.uid", ".write": "$uid === auth.uid" } } } } These rules grant access to a node matching the authenticated user's UID. This grants that each authenticated user can only access its own data. This means the user can only access the nodes that are under a node with its corresponding user UID. If there are other data published on the database, not under a node with the users' UID, that user can't access that data. For example, imagine our user UID is RjO3taAzMMXBB2Xmir2LQ. With our security rules, it can read and write data to the database under the node UsersData/RjO3taAzMMXBB2Xmir2LQ. You'll better understand how this works when you start working with the ESP32/ESP8266.

6) ESP32/ESP8266 Send Sensor Readings to the Realtime Databa3e

In this section, we'll program the ESP32 or ESP8266 boards to do the following tasks:
    Authenticate as a user with email and password (the user you set up in this section); Send sensor readings to the realtime database as an authorized user.

Parts Required

For this project, you need the following parts*: ESP32 or ESP8266 board (read ESP32 vs ESP8266); BME280 or any other sensor you're familiar with; Breadboard; Jumper wires. * you can also test the project with random values instead of sensor readings, or you can use any other sensor you're familiar with. You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Schematic Diagram

In this tutorial, we'll send BME280 sensor readings to the Firebase Realtime Database. So, you need to wire the BME280 sensor to your board. Follow one of the following schematic diagrams.

ESP32 with BME280

We're going to use I2C communication with the BME280 sensor module. For that, wire the sensor to the default ESP32 SCL (GPIO 22) and SDA (GPIO 21) pins, as shown in the following schematic diagram. Not familiar with the BME280 with the ESP32? Read this tutorial: ESP32 with BME280 Sensor using Arduino IDE (Pressure, Temperature, Humidity).

ESP8266 with BME280

We're going to use I2C communication with the BME280 sensor module. For that, wire the sensor to the ESP8266 SDA (GPIO 4) and SCL (GPIO 5) pins, as shown in the following schematic diagram. Not familiar with the BME280 with the ESP8266? Read this tutorial: ESP8266 with BME280 using Arduino IDE (Pressure, Temperature, Humidity).

Installing Libraries

For this project, you need to install the following libraries: Firebase ESP Client Library Adafruit BME280 Library Adafruit Unified Sensor Library

Installing Libraries VS Code

Follow the next instructions if you're using VS Code with the PlatformIO extension.
Install the Firebase-ESP-Client Library
There is a library with lots of examples to use Firebase with the ESP32: the Firebase-ESP-Client library. This library is compatible with both the ESP32 and ESP8266 boards. Click on the PIO Home icon and select the Libraries tab. Search for Firebase ESP Client. Select the Firebase Arduino Client Library for ESP8266 and ESP32. Then, click Add to Project and select the project you're working on. Install the BME280 Library In the Libraries tab, search for BME280. Select the Adafruit BME280 library. Then, click Add to Project and select the project you're working on. Also, change the monitor speed to 115200 by adding the following line to the platformio.ini file of your project: monitor_speed = 115200

Installation Arduino IDE

Follow this section if you're using Arduino IDE. You need to install the following libraries: Firebase ESP Client Library Adafruit BME280 Library Adafruit Unified Sensor Library Go to Sketch > Include Library > Manage Libraries, search for the libraries' names and install the libraries. For the Firebase Client library, select the Firebase Arduino Client Library for ESP8266 and ESP32. Now, you're all set to start programming the ESP32 and ESP8266 boards to interact with the database.

Send Sensor Readings to the Realtime Database Code

Copy the following code to your Arduino IDE or to the main.cpp file if you're using VS Code. You need to insert your network credentials, project API key, database URL, and the authorized user email and password. /* Rui Santos Complete project details at our blog: https://RandomNerdTutorials.com/esp32-esp8266-firebase-bme280-rtdb/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ #include <Arduino.h> #if defined(ESP32) #include <WiFi.h> #elif defined(ESP8266) #include <ESP8266WiFi.h> #endif #include <Firebase_ESP_Client.h> #include <Wire.h> #include <Adafruit_Sensor.h> #include <Adafruit_BME280.h> // Provide the token generation process info. #include "addons/TokenHelper.h" // Provide the RTDB payload printing info and other helper functions. #include "addons/RTDBHelper.h" // Insert your network credentials #define WIFI_SSID "REPLACE_WITH_YOUR_SSID" #define WIFI_PASSWORD "REPLACE_WITH_YOUR_PASSWORD" // Insert Firebase project API Key #define API_KEY "REPLACE_WITH_YOUR_PROJECT_API_KEY" // Insert Authorized Email and Corresponding Password #define USER_EMAIL "REPLACE_WITH_THE_USER_EMAIL" #define USER_PASSWORD "REPLACE_WITH_THE_USER_PASSWORD" // Insert RTDB URLefine the RTDB URL #define DATABASE_URL "REPLACE_WITH_YOUR_DATABASE_URL" // Define Firebase objects FirebaseData fbdo; FirebaseAuth auth; FirebaseConfig config; // Variable to save USER UID String uid; // Variables to save database paths String databasePath; String tempPath; String humPath; String presPath; // BME280 sensor Adafruit_BME280 bme; // I2C float temperature; float humidity; float pressure; // Timer variables (send new readings every three minutes) unsigned long sendDataPrevMillis = 0; unsigned long timerDelay = 180000; // Initialize BME280 void initBME(){ if (!bme.begin(0x76)) { Serial.println("Could not find a valid BME280 sensor, check wiring!"); while (1); } } // Initialize WiFi void initWiFi() { WiFi.begin(WIFI_SSID, WIFI_PASSWORD); Serial.print("Connecting to WiFi .."); while (WiFi.status() != WL_CONNECTED) { Serial.print('.'); delay(1000); } Serial.println(WiFi.localIP()); Serial.println(); } // Write float values to the database void sendFloat(String path, float value){ if (Firebase.RTDB.setFloat(&fbdo, path.c_str(), value)){ Serial.print("Writing value: "); Serial.print (value); Serial.print(" on the following path: "); Serial.println(path); Serial.println("PASSED"); Serial.println("PATH: " + fbdo.dataPath()); Serial.println("TYPE: " + fbdo.dataType()); } else { Serial.println("FAILED"); Serial.println("REASON: " + fbdo.errorReason()); } } void setup(){ Serial.begin(115200); // Initialize BME280 sensor initBME(); initWiFi(); // Assign the api key (required) config.api_key = API_KEY; // Assign the user sign in credentials auth.user.email = USER_EMAIL; auth.user.password = USER_PASSWORD; // Assign the RTDB URL (required) config.database_url = DATABASE_URL; Firebase.reconnectWiFi(true); fbdo.setResponseSize(4096); // Assign the callback function for the long running token generation task */ config.token_status_callback = tokenStatusCallback; //see addons/TokenHelper.h // Assign the maximum retry of token generation config.max_token_generation_retry = 5; // Initialize the library with the Firebase authen and config Firebase.begin(&config, &auth); // Getting the user UID might take a few seconds Serial.println("Getting User UID"); while ((auth.token.uid) == "") { Serial.print('.'); delay(1000); } // Print user UID uid = auth.token.uid.c_str(); Serial.print("User UID: "); Serial.println(uid); // Update database path databasePath = "/UsersData/" + uid; // Update database path for sensor readings tempPath = databasePath + "/temperature"; // --> UsersData/<user_uid>/temperature humPath = databasePath + "/humidity"; // --> UsersData/<user_uid>/humidity presPath = databasePath + "/pressure"; // --> UsersData/<user_uid>/pressure } void loop(){ // Send new readings to database if (Firebase.ready() && (millis() - sendDataPrevMillis > timerDelay || sendDataPrevMillis == 0)){ sendDataPrevMillis = millis(); // Get latest sensor readings temperature = bme.readTemperature(); humidity = bme.readHumidity(); pressure = bme.readPressure()/100.0F; // Send readings to database: sendFloat(tempPath, temperature); sendFloat(humPath, humidity); sendFloat(presPath, pressure); } } View raw code

How the Code Works

Continue reading to learn how the code works or skip to the demonstration section.

Include Libraries

First, include the required libraries. The WiFi.h library to connect the ESP32 to the internet (or the ESP8266WiFi.h library for the ESP8266 board), the Firebase_ESP_Client.h library to interface the boards with Firebase, and the Wire, Adafruit_Sensor, and Adafruit_BME280 to interface with the BME280 sensor. #include <Arduino.h> #if defined(ESP32) #include <WiFi.h> #elif defined(ESP8266) #include <ESP8266WiFi.h> #endif #include <Firebase_ESP_Client.h> #include <Wire.h> #include <Adafruit_Sensor.h> #include <Adafruit_BME280.h> You also need to include the following for the Firebase library to work. // Provide the token generation process info. #include "addons/TokenHelper.h" // Provide the RTDB payload printing info and other helper functions. #include "addons/RTDBHelper.h"

Network Credentials

Include your network credentials in the following lines so that your boards can connect to the internet using your local network. // Insert your network credentials #define WIFI_SSID "REPLACE_WITH_YOUR_SSID" #define WIFI_PASSWORD "REPLACE_WITH_YOUR_PASSWORD"

Firebase Project API Key, Firebase User, and Database URL

Insert your Firebase project API keythe one you've gotten in this section. #define API_KEY "REPLACE_WITH_YOUR_PROJECT_API_KEY" Insert the authorized email and the corresponding passwordthese are the details of the user you've added in this section. // Insert Authorized Email and Corresponding Password #define USER_EMAIL "REPLACE_WITH_THE_USER_EMAIL" #define USER_PASSWORD "REPLACE_WITH_THE_USER_PASSWORD" Insert your database URL in the following line: // Insert RTDB URLefine the RTDB URL #define DATABASE_URL "REPLACE_WITH_YOUR_DATABASE_URL"

Firebase Objects and Other Variables

The following line defines a FirebaseData object. FirebaseData fbdo; The next line defines a FirebaseAuth object needed for authentication. FirebaseAuth auth; Finally, the following line defines a FirebaseConfig object required for configuration data. FirebaseConfig config; The uid variable will be used to save the user's UID. We can get the user's UID after the authentication. String uid; The following variables will be used to save the nodes to where we'll send the sensor readings. We'll update these variables later in the code when we get the user UID. // Variables to save database paths String databasePath; String tempPath; String humPath; String presPath; Then, create an Adafruit_BME280 object called bme. This automatically creates a sensor object on the ESP32 or ESP8266 default I2C pins. Adafruit_BME280 bme; // I2C The following variables will hold the temperature, humidity, and pressure readings from the sensor. float temperature; float humidity; float pressure;

Delay Time

The sendDataPrevMillis and timerDelay variables are used to check the delay time between each send. In this example, we're setting the delay time to 3 minutes (18000 milliseconds). Once you test this project and check that everything is working as expected, we recommend increasing the delay. // Timer variables (send new readings every three minutes) unsigned long sendDataPrevMillis = 0; unsigned long timerDelay = 180000;

initBME()

The initBME() function initializes the BME280 library using the bme object created previously. Then, you should call this library in the setup(). void initBME(){ if (!bme.begin(0x76)) { Serial.println("Could not find a valid BME280 sensor, check wiring!"); while (1); } }

initWiFi()

The initWiFi() function connects your ESP to the internet using the network credentials provided. You must call this function later in the setup() to initialize WiFi. // Initialize WiFi void initWiFi() { WiFi.begin(WIFI_SSID, WIFI_PASSWORD); Serial.print("Connecting to WiFi .."); while (WiFi.status() != WL_CONNECTED) { Serial.print('.'); delay(1000); } Serial.println(WiFi.localIP()); Serial.println(); }

Send Data to the Database

To store data at a specific node in the Firebase Realtime Database, you can use the following functions: set, setInt, setFloat, setDouble, setString, setJSON, setArray, setBlob, and setFile. These functions return a boolean value indicating the success of the operation, which will be true if all of the following conditions are met: The server returns HTTP status code 200 The data types matched between request and response Only setBlob and setFile functions that make a silent request to Firebase server, thus no payload response returned. Learn more: ESP32: Getting Started with Firebase (Realtime Database) In our example, we'll send float variables, so we need to use the setFloat() function as follows. Firebase.RTDB.setFloat(&fbdo, "DATABASE_NODE", VALUE) The second argument refers to the database node (char variable) to which we want to send the data. The third argument is the data we want to send (float variable). As we'll need to send three float values, we created a functionthe sendFloat() function that accepts the node path and the value as arguments. // Write float values to the database void sendFloat(String path, float value){ if (Firebase.RTDB.setFloat(&fbdo, path.c_str(), value)){ Serial.print("Writing value: "); Serial.print (value); Serial.print(" on the following path: "); Serial.println(path); Serial.println("PASSED"); Serial.println("PATH: " + fbdo.dataPath()); Serial.println("TYPE: " + fbdo.dataType()); } else { Serial.println("FAILED"); Serial.println("REASON: " + fbdo.errorReason()); } } Later, we'll call that function in the loop() to send sensor readings.

setup()

In setup(), initialize the Serial Monitor for debugging purposes at a baud rate of 115200. Serial.begin(115200); Call the initBME() function to initialize the BME280 sensor. initBME(); Call the initWiFi() function to initialize WiFi. initWiFi(); Assign the API key to the Firebase configuration. config.api_key = API_KEY; The following lines assign the email and password to the Firebase authentication object. auth.user.email = USER_EMAIL; auth.user.password = USER_PASSWORD; Assign the database URL to the Firebase configuration object. config.database_url = DATABASE_URL; Add the following to the configuration object. // Assign the callback function for the long running token generation task config.token_status_callback = tokenStatusCallback; //see addons/TokenHelper.h // Assign the maximum retry of token generation config.max_token_generation_retry = 5; Initialize the Firebase library (authenticate) with the configuration and authentication settings we defined earlier. // Initialize the library with the Firebase authen and config Firebase.begin(&config, &auth); After initializing the library, we can get the user UID by calling auth.token.uid. Getting the user's UID might take some time, so we add a while loop that waits until we get it. // Getting the user UID might take a few seconds Serial.println("Getting User UID"); while ((auth.token.uid) == "") { Serial.print('.'); delay(1000); } Finally, we save the user's UID in the uid variable and print it in the Serial Monitor. uid = auth.token.uid.c_str(); Serial.print("User UID: "); Serial.print(uid); After getting the user UID, we can update the database node paths to include the user UID. That's what we do in the following lines. // Update database path for sensor readings tempPath = databasePath + "/temperature"; // --> UsersData/<user_uid>/temperature humPath = databasePath + "/humidity"; // --> UsersData/<user_uid>/humidity presPath = databasePath + "/pressure"; // --> UsersData/<user_uid>/pressure

loop()

In the loop(), check if it is time to send new readings: if (Firebase.ready() && (millis() - sendDataPrevMillis > timerDelay || sendDataPrevMillis == 0)){ sendDataPrevMillis = millis(); If it is, get the latest sensor readings from the BME280 sensor and save them on the temperature, humidity, and pressure variables. // Get latest sensor readings temperature = bme.readTemperature(); humidity = bme.readHumidity(); pressure = bme.readPressure()/100.0F; Finally, call the sendFloat() function to send the new readings to the database. Pass as arguments the node path and the value. // Send readings to database: sendFloat(tempPath, temperature); sendFloat(humPath, humidity); sendFloat(presPath, pressure);

Demonstration

Upload the previous code to your board. The code is compatible with both the ESP32 and ESP8266 boards. Don't forget to insert your network credentials, project API key, database URL, user email, and the corresponding password. After uploading the code, press the board RST button so that it starts running the code. It should authenticate to Firebase, get the user UID, and immediately send new readings to the database. Open the Serial Monitor at a baud rate of 115200 and check that everything is working as expected. Aditionally, go to the Realtime Database on your Firebase project interface and check that new readings are saved. Notice that it saves the data under a node with the own user UIDthis is a way to restrict access to the database. And that's it. You've successfully sent sensor readings to the Firebase Realtime Database, and you protected the data using database rules.

Wrapping Up

In this tutorial, you've learned how to authenticate the ESP32/ESP8266 as a user with email and password, send sensor readings to the database, and set up security rules to protect your database and restrict access. In PART 2, we'll create a Firebase Web App with authentication (login with email and password) that displays the sensor readings saved on the database. Only an authorized logged-in user can access the data. Later you'll be able to modify that project to display all sorts of data and restrict or allow access to the data to specific users. We hope you've found this tutorial useful. >> Continue to Part 2: ESP32/ESP8266: Firebase Web App to Display Sensor Readings (with Authentication) If you like Firebase projects, please take a look at our new eBook. We're sure you'll like it: Firebase Web App with ESP32 and ESP8266 Learn more about the ESP32 and ESP8266 with our resources: Free ESP32 Projects and Tutorials Free ESP8266 Projects and Tutorials Learn ESP32 with Arduino IDE Home Automation using ESP8266 Thanks for reading.
Build-Web-Servers-with-ESP32-and-ESP8266-eBook-2nd-Edition-500px-h

[eBook] Build Web Servers with ESP32 and ESP8266 (2nd Edition)

Build Web Server projects with the ESP32 and ESP8266 boards to control outputs and monitor sensors remotely. Learn HTML, CSS, JavaScript and client-server communication protocols DOWNLOAD Build Web Server projects with the ESP32 and ESP8266 boards to control outputs and monitor sensors remotely. Learn HTML, CSS, JavaScript and client-server communication protocols DOWNLOAD

NTP Time Setting Up Timezones and Daylight Saving Time

In this tutorial, you'll learn how to properly get the time with the ESP32 for your timezone and consider daylight saving time (if that's the case). The ESP32 will request the time from an NTP server, and the time will be automatically adjusted for your timezone with or without daylight saving time. Quick Answer: call setenv(TZ, timezone, 1), where timezone is one of the timezones listed here. Then, call tzset() to update to that timezone.
If you're getting started, we recommend taking a look at the following tutorial first to learn how to get date and time from an NTP server: ESP32 NTP Client-Server: Get Date and Time (Arduino IDE) In that previous tutorial, we've shown an option to set up your timezone. However, that example doesn't take into account daylight saving time. Continue reading this tutorial to learn how to set up the timezone and daylight saving time properly. Thanks to one of our readers (Hardy Maxa) who shared this information with us.

ESP32 Setting Timezone with Daylight Saving Time

According to documentation: To set local timezone, use setenv and tzset POSIX functions. First, call setenv to set TZ environment variable to the correct value depending on device location. Format of the time string is described in libc documentation. Next, call tzset to update C library runtime data for the new time zone. Once these steps are done, localtime function will return correct local time, taking time zone offset and daylight saving time into account. You can check a list of timezone string variables here. For example, I live in Porto. The timezone is Europe/Lisbon. From the list of timezone string variables, I see that the timezone string variable for my location is WET0WEST,M3.5.0/1,M10.5.0, so after connecting to the NTP server, to get the time for my location I need to call: setenv("TZ","WET0WEST,M3.5.0/1,M10.5.0",1); Followed by: tzset(); Let's look at a demo sketch to understand how it works and how to use it in your ESP32 project.

ESP32 Timezone and DST Example Sketch

The following example was provided by one of our followers (Hardy Maxa), we've just made a few modifications. Copy the following code to your Arduino IDE. // RTC demo for ESP32, that includes TZ and DST adjustments // Get the POSIX style TZ format string from https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv // Created by Hardy Maxa // Complete project details at: https://RandomNerdTutorials.com/esp32-ntp-timezones-daylight-saving/ #include <WiFi.h> #include "time.h" const char * ssid="REPLACE_WITH_YOUR_SSID"; const char * wifipw="REPLACE_WITH_YOUR_PASSWORD"; void setTimezone(String timezone){ Serial.printf(" Setting Timezone to %s\n",timezone.c_str()); setenv("TZ",timezone.c_str(),1); // Now adjust the TZ. Clock settings are adjusted to show the new local time tzset(); } void initTime(String timezone){ struct tm timeinfo; Serial.println("Setting up time"); configTime(0, 0, "pool.ntp.org"); // First connect to NTP server, with 0 TZ offset if(!getLocalTime(&timeinfo)){ Serial.println(" Failed to obtain time"); return; } Serial.println(" Got the time from NTP"); // Now we can set the real timezone setTimezone(timezone); } void printLocalTime(){ struct tm timeinfo; if(!getLocalTime(&timeinfo)){ Serial.println("Failed to obtain time 1"); return; } Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S zone %Z %z "); } void startWifi(){ WiFi.begin(ssid, wifipw); Serial.println("Connecting Wifi"); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(500); } Serial.print("Wifi RSSI="); Serial.println(WiFi.RSSI()); } void setTime(int yr, int month, int mday, int hr, int minute, int sec, int isDst){ struct tm tm; tm.tm_year = yr - 1900; // Set date tm.tm_mon = month-1; tm.tm_mday = mday; tm.tm_hour = hr; // Set time tm.tm_min = minute; tm.tm_sec = sec; tm.tm_isdst = isDst; // 1 or 0 time_t t = mktime(&tm); Serial.printf("Setting time: %s", asctime(&tm)); struct timeval now = { .tv_sec = t }; settimeofday(&now, NULL); } void setup(){ Serial.begin(115200); Serial.setDebugOutput(true); startWifi(); initTime("WET0WEST,M3.5.0/1,M10.5.0"); // Set for Melbourne/AU printLocalTime(); } void loop() { int i; // put your main code here, to run repeatedly: Serial.println("Lets show the time for a bit. Starting with TZ set for Melbourne/Australia"); for(i=0; i<10; i++){ delay(1000); printLocalTime(); } Serial.println(); Serial.println("Now - change timezones to Berlin"); setTimezone("CET-1CEST,M3.5.0,M10.5.0/3"); for(i=0; i<10; i++){ delay(1000); printLocalTime(); } Serial.println(); Serial.println("Now - Lets change back to Lisbon and watch Daylight savings take effect"); setTimezone("WET0WEST,M3.5.0/1,M10.5.0"); printLocalTime(); Serial.println(); Serial.println("Now change the time. 1 min before DST takes effect. (1st Sunday of Oct)"); Serial.println("AEST = Australian Eastern Standard Time. = UTC+10"); Serial.println("AEDT = Australian Eastern Daylight Time. = UTC+11"); setTime(2021,10,31,0,59,50,0); // Set it to 1 minute before daylight savings comes in. for(i=0; i<20; i++){ delay(1000); printLocalTime(); } Serial.println("Now change the time. 1 min before DST should finish. (1st Sunday of April)"); setTime(2021,3,28,1,59,50,1); // Set it to 1 minute before daylight savings comes in. Note. isDst=1 to indicate that the time we set is in DST. for(i=0; i<20; i++){ delay(1000); printLocalTime(); } // Now lets watch the time and see how long it takes for NTP to fix the clock Serial.println("Waiting for NTP update (expect in about 1 hour)"); while(1) { delay(1000); printLocalTime(); } } View raw code

How the Code Works

First, you need to include the WiFi library to connect the ESP32 to the internet (NTP server) and the time library to deal with time. #include <WiFi.h> #include "time.h" To set the timezone, we created a function called setTimezone() that accepts as an argument a timezone string. void setTimezone(String timezone){ Inside that function, we call the setenv() function for the TZ (timezone) parameter to set the timezone with whatever timezone you pass as an argument to the setTimezone() function. setenv("TZ",timezone.c_str(),1); // Now adjust the TZ. Clock settings are adjusted to show the new local time After that, call the tzset() function for the changes to take effect. tzset(); We won't go into detail about the other functions declared in the code because those were already explained in a previous tutorial: ESP32 NTP Client-Server: Get Date and Time (Arduino IDE)

setup()

In the setup(), we initialize Wi-Fi so that the ESP32 can connect to the internet to connect to the NTP server. initWifi(); Then, call the initTime() function and pass as argument the timezone String. In our case, for Lisbon/PT timezone. initTime("WET0WEST,M3.5.0/1,M10.5.0"); // Set for Lisbon/PT After that, print the current local time. printLocalTime();

loop()

In the loop() show the local time for ten seconds. Serial.println("Lets show the time for a bit. Starting with TZ set for Lisbon/Portugal"); for(i=0; i<10; i++){ delay(1000); printLocalTime(); } If at any time in your code you need to change the timezone, you can do it by calling the setTimezone() function and passing as an argument the timezone string. For example, the following lines change the timezone to Berlin and display the time for ten seconds. Serial.println(); Serial.println("Now - change timezones to Berlin"); setTimezone("CET-1CEST,M3.5.0,M10.5.0/3"); for(i=0; i<10; i++){ delay(1000); printLocalTime(); } Then, we go back to our local time by calling the setTimezone() function again with the Lisbon/PT timezone string variable. Serial.println(); Serial.println("Now - Lets change back to Lisbon and watch Daylight savings take effect"); setTimezone("WET0WEST,M3.5.0/1,M10.5.0"); printLocalTime(); To check if daylight saving time is taking effect, we'll change the time to 10 seconds before the winter time takes effect. In our case, it is on the last Sunday of October (it might be different for your location). Serial.println(); Serial.println("Now change the time. 1 min before DST takes effect. (Last Sunday of Oct)"); setTime(2021,10,31,0,59,50,0); // Set it to 1 minute before daylight savings comes in. Then, show the time for a while to check that it is adjusting the time, taking into account DST. for(i=0; i<20; i++){ delay(1000); printLocalTime(); } After this, let's change the time again to check if it changes to summer time when it comes the time. In our case, it is on the last Sunday of March. Serial.println("Now change the time. 1 min before DST should finish. (Last Sunday of March)"); setTime(2021,3,28,1,59,50,1); // Set it to 1 minute before daylight savings comes in. Note. isDst=1 to indicate that the time we set is in DST. Show the time for a while to check that it is adjusting to the summer time. for(i=0; i<20; i++){ delay(1000); printLocalTime(); }

Demonstration

Now, let's test the code. After inserting your network credentials, upload the code to your ESP32. After that, open the Serial Monitor at a baud rate of 115200 and press the ESP32 RST button to start running the code. First, it shows your local time with the timezone you've set on the code. After that, it will change to the other timezone you've set on the code. In our case, we set to Berlin. After that, you should see the change to winter time. In our case, when it's 2 a.m., the clock goes back one hour. We also check if it adjusts to summer time when it comes the time. In the example below, you can see that the clock goes forward one hour to set summer time.

Wrapping Up

This quick tutorial taught you how to set timezone with daylight saving time using the setenv() and tzset() functions.

Web Server: Display Sensor Readings in Gauges

Learn how to build a web server with the ESP32 to display sensor readings in gauges. As an example, we'll display temperature and humidity from a BME280 sensor in two different gauges: linear and radial. You can easily modify the project to plot any other data. To build the gauges, we'll use the canvas-gauges JavaScript library. We have a similar tutorial for the ESP8266 board: Web Server Display Sensor Readings in Gauges

Project Overview

This project will build a web server with the ESP32 that displays temperature and humidity readings from a BME280 sensor. We'll create a linear gauge that looks like a thermometer to display the temperature, and a radial gauge to display the humidity.

Server-Sent Events

The readings are updated automatically on the web page using Server-Sent Events (SSE). To learn more about SSE, you can read: ESP32 Web Server using Server-Sent Events (Update Sensor Readings Automatically)

Files Saved on the Filesystem

To keep our project better organized and easier to understand, we'll save the HTML, CSS, and JavaScript files to build the web page on the board's filesystem (SPIFFS). Learn more about building a web server with files saved on the filesystem: ESP32 Web Server using SPIFFS (SPI Flash File System)

Prerequisites

Make sure you check all the prerequisites in this section before continuing with the project.

1. Install ESP32 Board in Arduino IDE

We'll program the ESP32 using Arduino IDE. So, you must have the ESP32 add-on installed. Follow the next tutorial if you haven't already: Installing ESP32 Board in Arduino IDE (Windows, Mac OS X, Linux) If you want to use VS Code with the PlatformIO extension, follow the next tutorial instead to learn how to program the ESP32: Getting Started with VS Code and PlatformIO IDE for ESP32 and ESP8266 (Windows, Mac OS X, Linux Ubuntu)

2. Filesystem Uploader Plugin

To upload the HTML, CSS, and JavaScript files to the ESP32 flash memory (SPIFFS), we'll use a plugin for Arduino IDE: SPIFFS Filesystem uploader. Follow the next tutorial to install the filesystem uploader plugin: ESP32: Install SPIFFS FileSystem Uploader Plugin in Arduino IDE If you're using VS Code with the PlatformIO extension, read the following tutorial to learn how to upload files to the filesystem: ESP32 with VS Code and PlatformIO: Upload Files to Filesystem (SPIFFS)

3. Installing Libraries

To build this project, you need to install the following libraries: Adafruit_BME280 (Arduino Library Manager) Adafruit_Sensor library (Arduino Library Manager) Arduino_JSON library by Arduino version 0.1.0 (Arduino Library Manager) ESPAsyncWebServer (.zip folder); AsyncTCP (.zip folder). You can install the first three libraries using the Arduino Library Manager. Go to Sketch > Include Library > Manage Libraries and search for the libraries' names. The ESPAsyncWebServer and AsynTCP libraries aren't available to install through the Arduino Library Manager, so you need to copy the library files to the Arduino Installation Libraries folder. Alternatively, download the libraries' .zip folders, and then, in your Arduino IDE, go to Sketch > Include Library > Add .zip Library and select the libraries you've just downloaded.

Installing Libraries (VS Code + PlatformIO)

If you're programming the ESP32 using PlatformIO, you should add the following lines to the platformio.ini file to include the libraries (also change the Serial Monitor speed to 115200): monitor_speed = 115200 lib_deps = ESP Async WebServer arduino-libraries/Arduino_JSON @ 0.1.0 adafruit/Adafruit BME280 Library @ ^2.1.0 adafruit/Adafruit Unified Sensor @ ^1.1.4

Parts Required

To follow this tutorial you need the following parts: ESP32 (read Best ESP32 development boards) BME280 Sensor Jumper wires Breadboard You can use any other sensor, or display any other values that are useful for your project. If you don't have the sensor, you can also experiment with random values to learn how the project works. You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Schematic Diagram

We'll send temperature and humidity readings from a BME280 sensor. We're going to use I2C communication with the BME280 sensor module. For that, wire the sensor to the default ESP32 SCL (GPIO 22) and SDA (GPIO 21) pins, as shown in the following schematic diagram. Recommended reading: ESP32 Pinout Reference: Which GPIO pins should you use?

Organizing Your Files

To keep the project organized and make it easier to understand, we'll create four files to build the web server: Arduino sketch that handles the web server; index.html: to define the content of the web page; sytle.css: to style the web page; script.js: to program the behavior of the web pagehandle web server responses, events, create the gauges, etc. You should save the HTML, CSS, and JavaScript files inside a folder called data inside the Arduino sketch folder, as shown in the previous diagram. We'll upload these files to the ESP32 filesystem (SPIFFS). You can download all project files: Download All the Arduino Project Files

HTML File

Copy the following to the index.html file. <!DOCTYPE html> <html> <head> <title>ESP IOT DASHBOARD</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/png" href="favicon.png"> <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous"> <link rel="stylesheet" type="text/css" href="style.css"> <script src="http://cdn.rawgit.com/Mikhus/canvas-gauges/gh-pages/download/2.1.7/all/gauge.min.js"></script> </head> <body> <div> <h1>ESP WEB SERVER GAUGES</h2> </div> <div> <div> <div> <p>Temperature</p> <canvas></canvas> </div> <div> <p>Humidity</p> <canvas></canvas> </div> </div> </div> <script src="script.js"></script> </body> </html> View raw code The HTML file for this project is very simple. It includes the JavaScript canvas-gauges library in the head of the HTML file: <script src="https://cdn.rawgit.com/Mikhus/canvas-gauges/gh-pages/download/2.1.7/all/gauge.min.js"></script> There is a <canvas> tag with the id gauge-temperature where we'll render the temperature gauge later on. <canvas></canvas> There is also another <canvas> tag with the id gauge-humidity, where we'll render the humidity gauge later on. <canvas></canvas>

CSS File

Copy the following styles to your style.css file. It styles the web page with simple colors and styles. html { font-family: Arial, Helvetica, sans-serif; display: inline-block; text-align: center; } h1 { font-size: 1.8rem; color: white; } p { font-size: 1.4rem; } .topnav { overflow: hidden; background-color: #0A1128; } body { margin: 0; } .content { padding: 5%; } .card-grid { max-width: 1200px; margin: 0 auto; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); } .card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); } .card-title { font-size: 1.2rem; font-weight: bold; color: #034078 } View raw code

JavaScript File (creating the gauges)

Copy the following to the script.js file. // Get current sensor readings when the page loads window.addEventListener('load', getReadings); // Create Temperature Gauge var gaugeTemp = new LinearGauge({ renderTo: 'gauge-temperature', width: 120, height: 400, units: "Temperature C", minValue: 0, startAngle: 90, ticksAngle: 180, maxValue: 40, colorValueBoxRect: "#049faa", colorValueBoxRectEnd: "#049faa", colorValueBoxBackground: "#f1fbfc", valueDec: 2, valueInt: 2, majorTicks: [ "0", "5", "10", "15", "20", "25", "30", "35", "40" ], minorTicks: 4, strokeTicks: true, highlights: [ { "from": 30, "to": 40, "color": "rgba(200, 50, 50, .75)" } ], colorPlate: "#fff", colorBarProgress: "#CC2936", colorBarProgressEnd: "#049faa", borderShadowWidth: 0, borders: false, needleType: "arrow", needleWidth: 2, needleCircleSize: 7, needleCircleOuter: true, needleCircleInner: false, animationDuration: 1500, animationRule: "linear", barWidth: 10, }).draw(); // Create Humidity Gauge var gaugeHum = new RadialGauge({ renderTo: 'gauge-humidity', width: 300, height: 300, units: "Humidity (%)", minValue: 0, maxValue: 100, colorValueBoxRect: "#049faa", colorValueBoxRectEnd: "#049faa", colorValueBoxBackground: "#f1fbfc", valueInt: 2, majorTicks: [ "0", "20", "40", "60", "80", "100" ], minorTicks: 4, strokeTicks: true, highlights: [ { "from": 80, "to": 100, "color": "#03C0C1" } ], colorPlate: "#fff", borderShadowWidth: 0, borders: false, needleType: "line", colorNeedle: "#007F80", colorNeedleEnd: "#007F80", needleWidth: 2, needleCircleSize: 3, colorNeedleCircleOuter: "#007F80", needleCircleOuter: true, needleCircleInner: false, animationDuration: 1500, animationRule: "linear" }).draw(); // Function to get current readings on the webpage when it loads for the first time function getReadings(){ var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { var myObj = JSON.parse(this.responseText); console.log(myObj); var temp = myObj.temperature; var hum = myObj.humidity; gaugeTemp.value = temp; gaugeHum.value = hum; } }; xhr.open("GET", "/readings", true); xhr.send(); } if (!!window.EventSource) { var source = new EventSource('/events'); source.addEventListener('open', function(e) { console.log("Events Connected"); }, false); source.addEventListener('error', function(e) { if (e.target.readyState != EventSource.OPEN) { console.log("Events Disconnected"); } }, false); source.addEventListener('message', function(e) { console.log("message", e.data); }, false); source.addEventListener('new_readings', function(e) { console.log("new_readings", e.data); var myObj = JSON.parse(e.data); console.log(myObj); gaugeTemp.value = myObj.temperature; gaugeHum.value = myObj.humidity; }, false); } View raw code Here's a summary of what this code does: initializing the event source protocol; adding an event listener for the new_readings event; creating the gauges; getting the latest sensor readings from the new_readings event and display them in the corresponding gauges; making an HTTP GET request for the current sensor readings when you access the web page for the first time.

Get Readings

When you access the web page for the first time, we'll request the server to get the current sensor readings. Otherwise, we would have to wait for new sensor readings to arrive (via Server-Sent Events), which can take some time depending on the interval that you set on the server. Add an event listener that calls the getReadings function when the web page loads. // Get current sensor readings when the page loads window.addEventListener('load', getReadings); The window object represents an open window in a browser. The addEventListener() method sets up a function to be called when a certain event happens. In this case, we'll call the getReadings function when the page loads (load') to get the current sensor readings. Now, let's take a look at the getReadings function. Create a new XMLHttpRequest object. Then, send a GET request to the server on the /readings URL using the open() and send() methods. function getReadings() { var xhr = new XMLHttpRequest(); xhr.open("GET", "/readings", true); xhr.send(); } When we send that request, the ESP will send a response with the required information. So, we need to handle what happens when we receive the response. We'll use the onreadystatechange property that defines a function to be executed when the readyState property changes. The readyState property holds the status of the XMLHttpRequest. The response of the request is ready when the readyState is 4, and the status is 200. readyState = 4 means that the request finished and the response is ready; status = 200 means OK So, the request should look something like this: function getStates(){ var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { DO WHATEVER YOU WANT WITH THE RESPONSE } }; xhr.open("GET", "/states", true); xhr.send(); } The response sent by the ESP is the following text in JSON format (those are just arbitrary values). { "temperature" : "25.02", "humidity" : "64.01", } We need to convert the JSON string into a JSON object using the parse() method. The result is saved on the myObj variable. var myObj = JSON.parse(this.responseText); The myObj variable is a JSON object that contains the temperature and humidity readings. We want to update the gauges values with the corresponding readings. Updating the value of a gauge is straightforward. For example, our temperature gauge is called gaugeTemp (as we'll see later on), to update a value, we can simply call: gaugeTemp.value = NEW_VALUE. In our case, the new value is the temperature reading saved on the myObj JSON object. gaugeTemp.value = myObj.temperature; It is similar for the humidity (our humidity gauge is called gaugeHum). gaugeHum.value = myObj.humidity; Here's the complete getReadings() function. function getReadings(){ var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { var myObj = JSON.parse(this.responseText); console.log(myObj); var temp = myObj.temperature; var hum = myObj.humidity; gaugeTemp.value = temp; gaugeHum.value = hum; } }; xhr.open("GET", "/readings", true); xhr.send(); }

Creating the Gauges

The canvas-charts library allows you to build linear and radial gauges to display your readings. It provides several examples, and it is very simple to use. We recommend taking a look at the documentation and exploring all the gauges functionalities: Canvas-Gauges User Guide Configuration

Temperature Gauge

The following lines create the gauge to display the temperature. // Create Temperature Gauge var gaugeTemp = new LinearGauge({ renderTo: 'gauge-temperature', width: 120, height: 400, units: "Temperature C", minValue: 0, startAngle: 90, ticksAngle: 180, maxValue: 40, colorValueBoxRect: "#049faa", colorValueBoxRectEnd: "#049faa", colorValueBoxBackground: "#f1fbfc", valueDec: 2, valueInt: 2, majorTicks: [ "0", "5", "10", "15", "20", "25", "30", "35", "40" ], minorTicks: 4, strokeTicks: true, highlights: [ { "from": 30, "to": 40, "color": "rgba(200, 50, 50, .75)" } ], colorPlate: "#fff", colorBarProgress: "#CC2936", colorBarProgressEnd: "#049faa", borderShadowWidth: 0, borders: false, needleType: "arrow", needleWidth: 2, needleCircleSize: 7, needleCircleOuter: true, needleCircleInner: false, animationDuration: 1500, animationRule: "linear", barWidth: 10, }).draw(); To create a new linear gauge, use the new LinearGauge() method and pass as an argument the properties of the gauge. var gaugeTemp = new LinearGauge({ In the next line, define where you want to put the chart (it must be a <canvas> element). In our example, we want to place it in the <canvas> HTML element with the gauge-temperature idsee the HTML file section. renderTo: 'gauge-temperature', Then, we define other properties to customize our gauge. The names are self-explanatory, but we recommend taking a look at all possible configurations and changing the gauge to meet your needs. In the end, you need to apply the draw() method to actually display the gauge on the canvas. }).draw(); Special attention that if you need to change the gauge range, you need to change the minValue and maxValue properties: minValue: 0, maxValue: 40, You also need to adjust the majorTicks values for the values displayed on the axis. majorTicks: [ "0", "5", "10", "15", "20", "25", "30", "35", "40" ],

Humidity Gauge

Creating the humidity gauge is similar, but we use the new RadialGauge() function instead and it is rendered to the <canvas> with the gauge-humidity id. Notice that we apply the draw() method on the gauge so that it is drawn on the canvas. // Create Humidity Gauge var gaugeHum = new RadialGauge({ renderTo: 'gauge-humidity', width: 300, height: 300, units: "Humidity (%)", minValue: 0, maxValue: 100, colorValueBoxRect: "#049faa", colorValueBoxRectEnd: "#049faa", colorValueBoxBackground: "#f1fbfc", valueInt: 2, majorTicks: [ "0", "20", "40", "60", "80", "100" ], minorTicks: 4, strokeTicks: true, highlights: [ { "from": 80, "to": 100, "color": "#03C0C1" } ], colorPlate: "#fff", borderShadowWidth: 0, borders: false, needleType: "line", colorNeedle: "#007F80", colorNeedleEnd: "#007F80", needleWidth: 2, needleCircleSize: 3, colorNeedleCircleOuter: "#007F80", needleCircleOuter: true, needleCircleInner: false, animationDuration: 1500, animationRule: "linear" }).draw();

Handle events

Update the readings on the gauge when the client receives the readings on the new_readings event Create a new EventSource object and specify the URL of the page sending the updates. In our case, it's /events. if (!!window.EventSource) { var source = new EventSource('/events'); Once you've instantiated an event source, you can start listening for messages from the server with addEventListener(). These are the default event listeners, as shown here in the AsyncWebServer documentation. source.addEventListener('open', function(e) { console.log("Events Connected"); }, false); source.addEventListener('error', function(e) { if (e.target.readyState != EventSource.OPEN) { console.log("Events Disconnected"); } }, false); source.addEventListener('message', function(e) { console.log("message", e.data); }, false); Then, add the event listener for new_readings. source.addEventListener('new_readings', function(e) { When new readings are available, the ESP32 sends an event (new_readings) to the client. The following lines handle what happens when the browser receives that event. source.addEventListener('new_readings', function(e) { console.log("new_readings", e.data); var myObj = JSON.parse(e.data); console.log(myObj); gaugeTemp.value = myObj.temperature; gaugeHum.value = myObj.humidity; }, false); Basically, print the new readings on the browser console, convert the data into a JSON object and display the readings on the corresponding gauges.

Arduino Sketch

Copy the following code to your Arduino IDE or to the main.cpp file if you're using PlatformIO. You can also download all the files here. /********* Rui Santos Complete instructions at https://RandomNerdTutorials.com/esp32-web-server-gauges/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. *********/ #include <Arduino.h> #include <WiFi.h> #include <AsyncTCP.h> #include <ESPAsyncWebServer.h> #include "SPIFFS.h" #include <Arduino_JSON.h> #include <Adafruit_BME280.h> #include <Adafruit_Sensor.h> // Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; // Create AsyncWebServer object on port 80 AsyncWebServer server(80); // Create an Event Source on /events AsyncEventSource events("/events"); // Json Variable to Hold Sensor Readings JSONVar readings; // Timer variables unsigned long lastTime = 0; unsigned long timerDelay = 10000; // Create a sensor object Adafruit_BME280 bme; // BME280 connect to ESP32 I2C (GPIO 21 = SDA, GPIO 22 = SCL) // Init BME280 void initBME(){ if (!bme.begin(0x76)) { Serial.println("Could not find a valid BME280 sensor, check wiring!"); while (1); } } // Get Sensor Readings and return JSON object String getSensorReadings(){ readings["temperature"] = String(bme.readTemperature()); readings["humidity"] = String(bme.readHumidity()); String jsonString = JSON.stringify(readings); return jsonString; } // Initialize SPIFFS void initSPIFFS() { if (!SPIFFS.begin()) { Serial.println("An error has occurred while mounting SPIFFS"); } Serial.println("SPIFFS mounted successfully"); } // Initialize WiFi void initWiFi() { WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); Serial.print("Connecting to WiFi .."); while (WiFi.status() != WL_CONNECTED) { Serial.print('.'); delay(1000); } Serial.println(WiFi.localIP()); } void setup() { // Serial port for debugging purposes Serial.begin(115200); initBME(); initWiFi(); initSPIFFS(); // Web Server Root URL server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ request->send(SPIFFS, "/index.html", "text/html"); }); server.serveStatic("/", SPIFFS, "/"); // Request for the latest sensor readings server.on("/readings", HTTP_GET, [](AsyncWebServerRequest *request){ String json = getSensorReadings(); request->send(200, "application/json", json); json = String(); }); events.onConnect([](AsyncEventSourceClient *client){ if(client->lastId()){ Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId()); } // send event with message "hello!", id current millis // and set reconnect delay to 1 second client->send("hello!", NULL, millis(), 10000); }); server.addHandler(&events); // Start server server.begin(); } void loop() { if ((millis() - lastTime) > timerDelay) { // Send Events to the client with the Sensor Readings Every 10 seconds events.send("ping",NULL,millis()); events.send(getSensorReadings().c_str(),"new_readings" ,millis()); lastTime = millis(); } } View raw code

How the code works

Let's take a look at the code and see how it works to send readings to the client using server-sent events.

Including Libraries

The Adafruit_Sensor and Adafruit_BME280 libraries are needed to interface with the BME280 sensor. #include <Adafruit_BME280.h> #include <Adafruit_Sensor.h> The WiFi, ESPAsyncWebServer, and AsyncTCP libraries are used to create the web server. #include <WiFi.h> #include <AsyncTCP.h> #include <ESPAsyncWebServer.h> We'll use SPIFFS to save the files to build the web server. #include "SPIFFS.h" You also need to include the Arduino_JSON library to make it easier to handle JSON strings. #include <Arduino_JSON.h>

Network Credentials

Insert your network credentials in the following variables, so that the ESP32 can connect to your local network using Wi-Fi. const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD";

AsyncWebServer and AsyncEventSource

Create an AsyncWebServer object on port 80. AsyncWebServer server(80); The following line creates a new event source on /events. AsyncEventSource events("/events");

Declaring Variables

The readings variable is a JSON variable to hold the sensor readings in JSON format. JSONVar readings; The lastTime and the timerDelay variables will be used to update sensor readings every X number of seconds. As an example, we'll get new sensor readings every 30 seconds (30000 milliseconds). You can change that delay time in the timerDelay variable. unsigned long lastTime = 0; unsigned long timerDelay = 30000; Create an Adafruit_BME280 object called bme on the default ESP I2C pins. Adafruit_BME280 bme;

Initialize BME280 Sensor

The following function can be called to initialize the BME280 sensor. // Init BME280 void initBME(){ if (!bme.begin(0x76)) { Serial.println("Could not find a valid BME280 sensor, check wiring!"); while (1); } }

Get BME280 Readings

To get temperature and humidity from the BME280 temperature, use the following methods on the bme object: bme.readTemperature() bme.readHumidity() The getSensorReadings() function gets the sensor readings and saves them on the readings JSON array. // Get Sensor Readings and return JSON object String getSensorReadings(){ readings["temperature"] = String(bme.readTemperature()); readings["humidity"] = String(bme.readHumidity()); String jsonString = JSON.stringify(readings); return jsonString; } The readings array is then converted into a JSON string variable using the stringify() method and saved on the jsonString variable. The function returns the jsonString variable with the current sensor readings. The JSON string has the following format (the values are just arbitrary numbers for explanation purposes). { "temperature" : "25", "humidity" : "50" }

setup()

In the setup(), initialize the Serial Monitor, Wi-Fi, filesystem, and the BME280 sensor. void setup() { // Serial port for debugging purposes Serial.begin(115200); initBME(); initWiFi(); initSPIFFS();

Handle Requests

When you access the ESP32 IP address on the root / URL, send the text that is stored on the index.html file to build the web page. server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ request->send(SPIFFS, "/index.html", "text/html"); }); Serve the other static files requested by the client (style.css and script.js). server.serveStatic("/", SPIFFS, "/"); Send the JSON string with the current sensor readings when you receive a request on the /readings URL. // Request for the latest sensor readings server.on("/readings", HTTP_GET, [](AsyncWebServerRequest *request){ String json = getSensorReadings(); request->send(200, "application/json", json); json = String(); }); The json variable holds the return from the getSensorReadings() function. To send a JSON string as response, the send() method accepts as first argument the response code (200), the second is the content type (application/json) and finally the content (json variable).

Server Event Source

Set up the event source on the server. events.onConnect([](AsyncEventSourceClient *client){ if(client->lastId()){ Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId()); } // send event with message "hello!", id current millis // and set reconnect delay to 1 second client->send("hello!", NULL, millis(), 10000); }); server.addHandler(&events); Finally, start the server. server.begin();

loop()

In the loop(), send events to the browser with the newest sensor readings to update the web page every 30 seconds. events.send("ping",NULL,millis()); events.send(getSensorReadings().c_str(),"new_readings" ,millis()); Use the send() method on the events object and pass as an argument the content you want to send and the name of the event. In this case, we want to send the JSON string returned by the getSensorReadings() function. The send() method accepts a variable of type char, so we need to use the c_str() method to convert the variable. The name of the events is new_readings. Usually, we also send a ping message every X number of seconds. That line is not mandatory. It is used to check on the client side that the server is alive. events.send("ping",NULL,millis());

Uploading Code and Files

After inserting your network credentials, save the code. Go to Sketch > Show Sketch Folder, and create a folder called data. Inside that folder, you should save the HTML, CSS, and JavaScript files. Then, upload the code to your ESP32 board. Make sure you have the right board and COM port selected. Also, make sure you've added your network credentials. After uploading the code, you need to upload the files. Go to Tools > ESP32 Data Sketch Upload and wait for the files to be uploaded. When everything is successfully uploaded, open the Serial Monitor at a baud rate of 115200. Press the ESP32 EN/RST button, and it should print the ESP32 IP address.

Demonstration

Open your browser and type the ESP32 IP address. You should get access to the web page that shows the gauges with the latest sensor readings. You can also check your gauges using your smartphone (the web page is mobile responsive).

Wrapping Up

In this tutorial you've learned how to create a web server to display sensor readings in linear and radial gauges. As an example, we displayed temperature and humidity from a BME280 sensor. You can use those gauges to display any other values that may make sense for your project.